382 lines
28 KiB
HTML
382 lines
28 KiB
HTML
<!DOCTYPE html>
|
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
|
<head>
|
|
<meta charset="utf-8"/>
|
|
<title>Clojure Guides: Clojure Macros and Metaprogramming</title>
|
|
|
|
|
|
<meta name="description" content="This guide covers:">
|
|
|
|
<meta property="og:description" content="This guide covers:">
|
|
|
|
<meta property="og:url" content="https://clojure-doc.github.io/articles/language/macros/" />
|
|
<meta property="og:title" content="Clojure Macros and Metaprogramming" />
|
|
<meta property="og:type" content="article" />
|
|
|
|
<link rel="canonical" href="https://clojure-doc.github.io/articles/language/macros/">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link href="https://fonts.googleapis.com/css?family=Alegreya:400italic,700italic,400,700" rel="stylesheet"
|
|
type="text/css">
|
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css">
|
|
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.7.0/styles/default.min.css">
|
|
<link href="../../../css/screen.css" rel="stylesheet" type="text/css" />
|
|
</head>
|
|
<body>
|
|
|
|
|
|
<nav class="navbar navbar-default">
|
|
<div class="container">
|
|
<div class="navbar-header">
|
|
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
|
|
<span class="sr-only">Toggle navigation</span>
|
|
<span class="icon-bar"></span>
|
|
<span class="icon-bar"></span>
|
|
<span class="icon-bar"></span>
|
|
</button>
|
|
<a class="navbar-brand" href="../../../index.html">Clojure Guides</a>
|
|
</div>
|
|
<div id="navbar" class="navbar-collapse collapse">
|
|
<ul class="nav navbar-nav navbar-right">
|
|
<li ><a href="../../../index.html">Home</a></li>
|
|
<li><a href="https://github.com/clojure-doc/clojure-doc.github.io">Contribute</a></li>
|
|
</ul>
|
|
</div><!--/.nav-collapse -->
|
|
</div><!--/.container-fluid -->
|
|
</nav>
|
|
|
|
|
|
<div class="container">
|
|
|
|
|
|
<div class="row">
|
|
<div class="col-lg-9">
|
|
<div id="content">
|
|
|
|
<div id="custom-page">
|
|
<div id="page-header">
|
|
<h2>Clojure Macros and Metaprogramming</h2>
|
|
</div>
|
|
|
|
<p>This guide covers:</p><ul><li>Clojure macros</li><li>the Clojure compilation process</li></ul><p>This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/3.0/">Creative Commons
|
|
Attribution 3.0 Unported License</a> (including images &
|
|
stylesheets). The source is available <a href="https://github.com/clojure-doc/clojure-doc.github.io">on
|
|
Github</a>.</p><h2 id="what-version-of-clojure-does-this-guide-cover">What Version of Clojure Does This Guide Cover?</h2><p>This guide covers Clojure 1.5.</p><h2 id="before-you-read-this-guide">Before You Read This Guide</h2><p>This is one of the most hardcore guides of the entire Clojure documentation
|
|
project. It describes concepts that are relatively unique to the Lisp family of languages
|
|
that Clojure belongs to. Understanding them may take some time for folks without
|
|
a metaprogramming background. Don't let this learning curve
|
|
discourage you.</p><p>If some parts are not clear, please ask for clarification <a href="https://groups.google.com/forum/?fromgroups#!forum/clojure">on the
|
|
mailing
|
|
list</a> or
|
|
<a href="https://github.com/clojure-doc/clojure-doc.github.io/issues">file an issue</a> on GitHub.
|
|
We will work hard on making this guide easy to follow with edits and
|
|
images to illustrate the concepts.</p><h2 id="overview">Overview</h2><p>Clojure is a dialect of Lisp and while it departs with some features of "traditional" Lisps,
|
|
the fundamentals are there. One very powerful feature that comes with it is <em>macros</em>,
|
|
a way to do metaprogramming using the language itself. This is pretty different from
|
|
other languages known for good metaprogramming capabilities (e.g. Ruby) in that
|
|
in Clojure, metaprogramming does not mean string generation. Instead, it means
|
|
constructing a tree [of S-expressions, or lists]. This enables very powerful
|
|
DSLs (domain-specific languages).</p><h2 id="compile-time-and-run-time">Compile Time and Run Time</h2><p>Clojure is a compiled language. The compiler reads source files or strings,
|
|
produces data structures (aka the AST) and performs <em>macroexpansion</em>. Macros are evaluated at
|
|
<em>compile time</em> and produce modified data structures that are compiled to the JVM
|
|
bytecode. That bytecode is executed at <em>run time</em>.</p><p>Clojure code is compiled when it is loaded with <code>clojure.core/load</code> or <code>clojure.core/require</code>
|
|
or can be ahead of time (AOT compilation) using tools such as <a href="http://leiningen.org">Leiningen</a>
|
|
or the <a href="../../ecosystem/maven/index.html">Clojure Maven plugin</a>.</p><h2 id="clojure-reader">Clojure Reader</h2><p>Reader is another name for parser. Unlike many other languages, reader in Clojure
|
|
can be extended in the language itself. It is also exposed to the language
|
|
with <code>clojure.core/read</code> and <code>clojure.core/read-string</code> functions that
|
|
return data structures:</p><pre style="visibility:hidden; height:0;"><code class="klipse-clojure nohighlight">
|
|
(require '[cljs.reader :refer [read-string]])
|
|
</code></pre><pre><code class="klipse-clojure nohighlight">(read-string "(if true :truth :false)")
|
|
;= (if true :truth :false)
|
|
</code></pre><p>Here we got back a list that is not evaluated.</p><p>The Reader produces data structures (in part that's why "code is data" in homoiconic
|
|
languages) that are then evaluated:</p><ul><li>Literals (e.g., strings, integers, vectors) evaluate to themselves</li><li>Lists evaluate to invocations (calls) of functions and so on</li><li>Symbols are resolved to a var value</li></ul><p>Expressions that can be evaluated (invoked) are known as <em>forms</em>. Forms consist of:</p><ul><li>Functions</li><li>Macros</li><li>Special forms</li></ul><h3 id="special-forms">Special Forms</h3><p>The reader parses some forms in special ways that are not consistent
|
|
with the rest of Clojure's syntax.</p><p>Such forms are called <em>special forms</em>. They consist of</p><ul><li>. (the dot special form)</li><li>new</li><li>set!</li><li>def</li><li>var</li><li>fn* (<code>fn</code> without destructuring)</li><li>if</li><li>case* (internal implementation of <code>case</code>)</li><li>do</li><li>let* (<code>let</code> without destructuring)</li><li>letfn* (<code>letfn</code> without destructuring)</li><li>clojure.core/import* (<code>import</code>)</li><li>quote</li><li>loop* (<code>loop</code> without destructuring)</li><li>recur</li><li>throw, try, catch, finally</li><li>deftype* (internals of <code>deftype</code>)</li><li>reify* (internals of <code>reify</code>)</li><li>monitor-enter, monitor-exit</li></ul><p>Some special forms are used directly in user code (like <code>do</code> and <code>if</code>), while others
|
|
are only used to build more user friendly interfaces (like using <code>deftype</code> over the special form <code>deftype*</code>).</p><h2 id="first-taste-of-macros">First Taste of Macros</h2><p>Some programming languages include an <code>unless</code> expression (or statement) that is
|
|
the opposite of <code>if</code>. Clojure is not one of them but it can be added by using
|
|
a macro:</p><pre style="visibility:hidden; height:0;"><code class="klipse-clojure nohighlight">
|
|
(require '[chivorcam.core :refer [defmacro defmacfn]])
|
|
</code></pre><pre><code class="klipse-clojure nohighlight">(defmacro unless
|
|
"Similar to if but negates the condition"
|
|
[condition & forms]
|
|
`(if (not ~condition)
|
|
~@forms))
|
|
</code></pre><p>Macros are defined using the <code>clojure.core/defmacro</code> function that takes
|
|
macro name as a symbol, an optional documentation string, a vector
|
|
of arguments and the macro body.</p><p>This macro can be used like similarly to the <code>if</code> form:</p><pre><code class="klipse-clojure nohighlight">(unless (= 1 2)
|
|
"one does not equal two"
|
|
"one equals two. How come?")
|
|
</code></pre><p>Just like the <code>if</code> special form, this macro produces an expression that
|
|
returns a value:</p><pre><code class="clojure">(unless (= 1 2)
|
|
"one does not equal two"
|
|
"one equals two. How come?")
|
|
</code></pre><p>in fact, this is because the macro piggybacks on the <code>if</code> form.
|
|
To see what the macro expands to, we can use <code>clojure.core/macroexpand-1</code>:</p><pre><code class="klipse-clojure nohighlight">(macroexpand-1 '(unless (= 1 2) true false))
|
|
;= (if (clojure.core/not (= 1 2)) true false)
|
|
</code></pre><p>This simplistic macro and the way we expanded it with <code>macroexpand-1</code>
|
|
demonstrates three features of the Clojure reader that are used when
|
|
writing macros:</p><ul><li>Quote (')</li><li>Syntax quote (`)</li><li>Unquote (~)</li><li>Unquote splicing (~@)</li></ul><h2 id="quote">Quote</h2><p>Quote supresses evaluation of the form that follows it. In other words,
|
|
instead of being treated as an invocation, it will be treated as a list.</p><p>Compare:</p><pre><code class="klipse-clojure nohighlight">;; this form is evaluated by calling the clojure.core/+ function
|
|
(+ 1 2 3)
|
|
;= 6
|
|
</code></pre><pre><code class="klipse-clojure nohighlight">;; quote supresses evaluation so the + is treated as a regular
|
|
;; list element
|
|
'(+ 1 2 3)
|
|
;= (+ 1 2 3)
|
|
</code></pre><p>The syntax quote supresses evaluation of the form that follows it and
|
|
all nested forms. It is similar to templating languages where parts
|
|
of the template are "fixed" and parts are "inserted" (evaluated).
|
|
The syntax quote makes the form that follows it "a template".</p><h2 id="unquote">Unquote</h2><p>Unquote then is how parts of the template are forced to be evaluated
|
|
(act similarly to variables in templates in templating languages).</p><p>Let's take another look at the same <code>unless</code> macro:</p><pre><code class="klipse-clojure nohighlight">(defmacro unless
|
|
[condition & forms]
|
|
`(if (not ~condition)
|
|
~@forms))
|
|
</code></pre><p>and how we invoke it:</p><pre><code class="klipse-clojure nohighlight">(unless (= 1 2)
|
|
"one does not equal two"
|
|
"one equals two. How come?")
|
|
</code></pre><p>When the macro is expanded, the condition local in this example has the value
|
|
of <code>(= 1 2)</code> (a list). We want <code>unless</code> to perform boolean evaluation on it,
|
|
and that's what unquote (<code>~</code>) does as can be seen from macroexpansion:</p><pre><code class="klipse-clojure nohighlight">(macroexpand-1 '(unless (= 1 2) true false))
|
|
;= (if (clojure.core/not (= 1 2)) true false)
|
|
</code></pre><p>Compare this with what the macro expands to when the unquote is removed:</p><pre><code class="klipse-clojure nohighlight">;; incorrect, missing unquote!
|
|
(defmacro unless
|
|
[condition & forms]
|
|
`(if (not condition)
|
|
~@forms))
|
|
|
|
(macroexpand-1 '(unless (= 1 2) true false))
|
|
;= (if (clojure.core/not user/condition) true false)
|
|
</code></pre><h3 id="implementation-details">Implementation Details</h3><p>The unquote operator is replaced by the reader with a call to a core
|
|
Clojure function, <code>clojure.core/unquote</code>.</p><h2 id="unquote-splicing">Unquote-splicing</h2><p>Some macros take multiple forms. This is common in DSLs, for example.
|
|
Each of those forms is often need to be quoted and concatenated.</p><p>The unquote-splicing operator (<code>~@</code>) is a convenient way to do it:</p><pre><code class="klipse-clojure nohighlight">(defmacro unsplice
|
|
[& coll]
|
|
`(do ~@coll))
|
|
</code></pre><pre><code class="klipse-clojure nohighlight">(macroexpand-1 '(unsplice (def a 1) (def b 2)))
|
|
;= (do (def a 1) (def b 2))
|
|
</code></pre><pre><code class="klipse-clojure nohighlight">(unsplice (def a 1) (def b 2))
|
|
;= #'user/b
|
|
</code></pre><pre><code class="klipse-clojure nohighlight">a
|
|
;= 1
|
|
</code></pre><pre><code class="klipse-clojure nohighlight">b
|
|
;= 2
|
|
</code></pre><h3 id="implementation-details-1">Implementation Details</h3><p>The unquote-splicing operator is replaced by the reader with a call to a core
|
|
Clojure function, <code>clojure.core/unquote-splicing</code>.</p><h2 id="macro-hygiene-and-gensym">Macro Hygiene and gensym</h2><p>When writing a macro, there is a possibility that the macro will interact with
|
|
vars or locals outside of it in unexpected ways, for example, by <a href="http://en.wikipedia.org/wiki/Variable_shadowing">shadowing</a> them.
|
|
Such macros are known as <em>unhygienic macros</em>.</p><p>Clojure does not implement a full solution to hygienic macros but
|
|
provides solutions to the biggest pitfalls of unhygienic macros by enforcing several restrictions:</p><ul><li>Symbols within a syntax quoted form are namespace-qualified</li><li>Unique symbol name generation (aka <em>gensyms</em>)</li></ul><h3 id="namespace-qualification-within-syntax-quote">Namespace Qualification Within Syntax Quote</h3><p>To demonstrate this behavior of syntax quote, consider the following example
|
|
that replaces values "yes" and "no" with true and false, respectively, at compile
|
|
time:</p><pre><code class="klipse-clojure nohighlight">(defmacro yes-no->boolean
|
|
[val]
|
|
`(let [b (= ~val "yes")]
|
|
b))
|
|
;= #'user/yes-no->boolean
|
|
</code></pre><pre><code class="klipse-clojure nohighlight">(macroexpand-1 '(yes-no->boolean "yes"))
|
|
;= (clojure.core/let [user/b (clojure.core/= "yes" "yes")] user/b)
|
|
</code></pre><p>Macroexpansion demonstrates that the Clojure compiler makes the <code>b</code> symbol namespace-qualified
|
|
(<code>user</code> is the default namespace in the Clojure REPL). This helps avoid var and local
|
|
shadowing.</p><p>Note: Special forms are not necessarily qualified. See section 'Special Forms in Detail'.</p><h3 id="generated-symbols-gensyms">Generated Symbols (gensyms)</h3><p>Automatic namespace generation is fine in some cases, but not every time. Sometimes
|
|
a symbol name that is unique in the macro scope is necessary.</p><p>Unique symbols names can be generated with the <code>clojure.core/gensym</code> function that
|
|
take an optional base string:</p><pre><code class="klipse-clojure nohighlight">(gensym)
|
|
;= G__54
|
|
</code></pre><pre><code class="klipse-clojure nohighlight">(gensym "base")
|
|
;= base57
|
|
</code></pre><p>There is a shortcut: if a symbol ends in <code>#</code> within a syntax quote form, it will be
|
|
expanded by the compiler into a gensym (aka. an auto-gensym):</p><pre><code class="clojure">(defmacro yes-no->boolean
|
|
[val]
|
|
`(let [b# (= ~val "yes")]
|
|
b#))
|
|
;= #'user/yes-no->boolean
|
|
</code></pre><pre><code class="klipse-clojure nohighlight">(macroexpand-1 '(yes-no->boolean "yes"))
|
|
;= (clojure.core/let [b__148__auto__ (clojure.core/= "yes" "yes")] b__148__auto__)
|
|
</code></pre><p>The name that replaced <code>b#</code> was generated by the compiler to make unwanted variable
|
|
capture very unlikely in practice, and impossible if all bindings are named with auto-gensym.</p><p>Theoretically, Clojure's approach to generating uncaptured gensyms (incrementing a global counter) can be circumvented
|
|
via a mischievous macro or very bad luck.</p><p>Tip:
|
|
Avoid code with <code>__</code> in local binding names. This ensures
|
|
auto-gensyms are <em>never</em> captured in unwanted ways.</p><h2 id="macroexpansions">Macroexpansions</h2><p>During macro development, it is important to be able to test the macro
|
|
and see what data structures the macro expands to. This can be done
|
|
with two functions in the core Clojure library:</p><ul><li><code>clojure.core/macroexpand-1</code></li><li><code>clojure.core/macroexpand</code></li><li><code>clojure.walk/macroexpand-all</code></li></ul><p>The difference between the two is that <code>macroexpand-1</code> will expand the macro
|
|
only once. If the result contains calls to other macros, those won't be expanded.
|
|
<code>macroexpand</code>, however, will continue expanding all macros until the top level form
|
|
is no longer a macro.</p><p>Both macroexpansion functions take quoted forms.</p><p>Macro expansion functions can be used to find out that <code>when</code> is a macro implemented on top of
|
|
the <code>if</code> special form, for example:</p><pre><code class="klipse-clojure nohighlight">(macroexpand '(when true 1 42))
|
|
</code></pre><h3 id="full-macroexpansion">Full Macroexpansion</h3><p>Neither <code>macroexpand-1</code> nor <code>macroexpand</code> expand nested
|
|
forms. To fully expand macros including those in nested forms, there is <code>clojure.walk/macroexpand-all</code>,
|
|
which, however, is not part of Clojure core and does not behave exactly the same way
|
|
the compiler does.</p><h2 id="difference-between-quote-and-syntax-quote">Difference Between Quote and Syntax Quote</h2><p>The key difference between quote and syntax quote is that
|
|
symbols within a syntax quoted form are automatically namespace-qualified.</p><h2 id="security-considerations">Security Considerations</h2><p><code>clojure.core/read-string</code> <em>can execute arbitrary code</em> and <em>must not</em> be used
|
|
on inputs coming from untrusted sources. This behavior is controlled by the <code>clojure.core/*read-eval*</code>
|
|
var. Starting with Clojure 1.5, the default value of <code>*read-eval*</code> is <code>false</code>.</p><p><code>*read-eval*</code> can be disabled via a property when starting the JVM:</p><pre><code>-Dclojure.read.eval=false
|
|
</code></pre><p>When reading Clojure forms from untrusted sources, use <code>clojure.edn/read-string</code>, which is
|
|
does not perform arbitrary code execution and is safer. <code>clojure.edn/read-string</code> implements
|
|
the <a href="https://github.com/edn-format/edn">EDN format</a>, a subset of Clojure syntax for data
|
|
structures. <code>clojure.edn</code> was introduced in Clojure 1.5.</p><h2 id="special-forms-in-detail">Special Forms in Detail</h2><p>Special forms are restrictive in their use and do not interact cleanly with several area of Clojure.</p><ul><li><p>Special forms must be a list with a special name as the first element.</p><p>A special name in a higher-order context is not a special form.</p><pre><code class="klipse-clojure nohighlight">do
|
|
;; CompilerException java.lang.RuntimeException: Unable to resolve symbol: do in this context, compiling:(NO_SOURCE_PATH:0:0)
|
|
</code></pre><p>Macros have a similar restriction, but notice: the macro's var is identified in the error while
|
|
special names have no meaning at all outside the first element of a list.</p><pre><code class="klipse-clojure nohighlight">dosync
|
|
;; CompilerException java.lang.RuntimeException: Can't take value of a macro: #'clojure.core/dosync, compiling:(NO_SOURCE_PATH:0:0)
|
|
</code></pre></li><li><p>Special form names are not namespace-qualified.</p><p>Most special forms (all except <code>clojure.core/import*</code>) are not namespace
|
|
qualified. The reader must circumvent syntax quote's policy of namespace-qualifying
|
|
all symbols.</p><pre><code class="klipse-clojure nohighlight">`a
|
|
;; user/a
|
|
</code></pre><pre><code class="klipse-clojure nohighlight">`do
|
|
;; do
|
|
</code></pre><pre><code class="clojure">user=> `if
|
|
if
|
|
user=> `import*
|
|
user/import*
|
|
</code></pre></li><li><p>Special forms conflict with local scope.</p><p>Never use special names as local binding or global variable names.</p><pre><code class="clojure">(let [do 1] do)
|
|
;;; nil
|
|
</code></pre><p>Ouch!</p><p>This includes destructuring:</p><pre><code class="clojure">user=> (let [{:keys [do]} {:do 1}] do)
|
|
nil
|
|
</code></pre><p>Note: Be wary of maps with keyword keys with special names, they are more
|
|
likely to be destructured this way.</p></li></ul><p>Keep these special cases in mind as you work through the tutorial.</p><h2 id="contributors">Contributors</h2><ul><li>Michael Klishin <a href="mailto:michael@defprotocol.org">michael@defprotocol.org</a>, 2013 (original author)</li><li>Ambrose Bonnaire-Sergeant <a href="mailto:abonnairesergeant@gmail.com">abonnairesergeant@gmail.com</a>, 2013</li></ul>
|
|
|
|
<div id="prev-next">
|
|
|
|
<a href="../interop/index.html">« Clojure interoperability with Java</a>
|
|
|
|
|
|
||
|
|
|
|
|
|
<a href="../polymorphism/index.html">Polymorphism in Clojure: Protocols and Multimethods »</a>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3">
|
|
<div id="sidebar">
|
|
<h3>Links</h3>
|
|
<ul id="links">
|
|
|
|
<li><a href="../../about/index.html">About</a></li>
|
|
|
|
<li><a href="../../content/index.html">Table of Contents</a></li>
|
|
|
|
<li><a href="../../tutorials/getting_started/index.html">Getting Started with Clojure</a></li>
|
|
|
|
<li><a href="../../tutorials/introduction/index.html">Introduction to Clojure</a></li>
|
|
|
|
<li><a href="../../tutorials/emacs/index.html">Clojure with Emacs</a></li>
|
|
|
|
<li><a href="../../tutorials/vim_fireplace/index.html">Clojure with Vim and fireplace.vim</a></li>
|
|
|
|
<li><a href="../../tutorials/eclipse/index.html">Starting with Eclipse and Counterclockwise For Clojure Development</a></li>
|
|
|
|
<li><a href="../../tutorials/basic_web_development/index.html">Basic Web Development</a></li>
|
|
|
|
<li><a href="../../tutorials/parsing_xml_with_zippers/index.html">Parsing XML in Clojure</a></li>
|
|
|
|
<li><a href="../../tutorials/growing_a_dsl_with_clojure/index.html">Growing a DSL with Clojure</a></li>
|
|
|
|
<li><a href="../core_overview/index.html">Overview of clojure.core, the standard Clojure library</a></li>
|
|
|
|
<li><a href="../namespaces/index.html">Clojure Namespaces and Vars</a></li>
|
|
|
|
<li><a href="../collections_and_sequences/index.html">Collections and Sequences in Clojure</a></li>
|
|
|
|
<li><a href="../functions/index.html">Functions in Clojure</a></li>
|
|
|
|
<li><a href="../laziness/index.html">Laziness in Clojure</a></li>
|
|
|
|
<li><a href="../interop/index.html">Clojure interoperability with Java</a></li>
|
|
|
|
<li><a href="index.html">Clojure Macros and Metaprogramming</a></li>
|
|
|
|
<li><a href="../polymorphism/index.html">Polymorphism in Clojure: Protocols and Multimethods</a></li>
|
|
|
|
<li><a href="../concurrency_and_parallelism/index.html">Concurrency and Parallelism in Clojure</a></li>
|
|
|
|
<li><a href="../glossary/index.html">Clojure Terminology Guide</a></li>
|
|
|
|
<li><a href="../../ecosystem/libraries_directory/index.html">A Directory of Clojure Libraries</a></li>
|
|
|
|
<li><a href="../../ecosystem/libraries_authoring/index.html">Library Development and Distribution</a></li>
|
|
|
|
<li><a href="../../ecosystem/generating_documentation/index.html">Generating Documentation</a></li>
|
|
|
|
<li><a href="../../ecosystem/data_processing/index.html">Data Processing (Help Wanted)</a></li>
|
|
|
|
<li><a href="../../ecosystem/web_development/index.html">Web Development (Overview)</a></li>
|
|
|
|
<li><a href="../../ecosystem/maven/index.html">How to use Maven to build Clojure projects</a></li>
|
|
|
|
<li><a href="../../ecosystem/community/index.html">Clojure Community</a></li>
|
|
|
|
<li><a href="../../ecosystem/user_groups/index.html">Clojure User Groups</a></li>
|
|
|
|
<li><a href="../../ecosystem/running_cljug/index.html">Running a Clojure User Group</a></li>
|
|
|
|
<li><a href="../../ecosystem/books/index.html">Books about Clojure and ClojureScript</a></li>
|
|
|
|
<li><a href="../../cookbooks/data_structures/index.html">Data Structures (Help wanted)</a></li>
|
|
|
|
<li><a href="../../cookbooks/strings/index.html">Strings</a></li>
|
|
|
|
<li><a href="../../cookbooks/math/index.html">Mathematics with Clojure</a></li>
|
|
|
|
<li><a href="../../cookbooks/date_and_time/index.html">Date and Time (Help wanted)</a></li>
|
|
|
|
<li><a href="../../cookbooks/files_and_directories/index.html">Working with Files and Directories in Clojure</a></li>
|
|
|
|
<li><a href="../../cookbooks/middleware/index.html">Middleware in Clojure</a></li>
|
|
|
|
<li><a href="../../ecosystem/java_jdbc/home.html">java.jdbc - Getting Started</a></li>
|
|
|
|
<li><a href="../../ecosystem/java_jdbc/using_sql.html">java.jdbc - Manipulating data with SQL</a></li>
|
|
|
|
<li><a href="../../ecosystem/java_jdbc/using_ddl.html">java.jdbc - Using DDL and Metadata</a></li>
|
|
|
|
<li><a href="../../ecosystem/java_jdbc/reusing_connections.html">java.jdbc - How to reuse database connections</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/home/index.html">core.typed - User Documentation Home</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/user_documentation/index.html">core.typed - User Documentation</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/rationale/index.html">core.typed - Rationale</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/quick_guide.html">core.typed - Quick Guide</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/start/introduction_and_motivation/index.html">core.typed - Getting Started: Introduction and Motivation</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/types/index.html">core.typed - Types</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/start/annotations/index.html">core.typed - Annotations</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/poly_fn/index.html">core.typed - Polymorphic Functions</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/filters/index.html">core.typed - Filters</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/mm_protocol_datatypes/index.html">core.typed - Protocols</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/loops/index.html">core.typed - Looping constructs</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/function_types/index.html">core.typed - Functions</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/limitations/index.html">core.typed - Limitations</a></li>
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<footer>Copyright © 2021 Multiple Authors
|
|
<p style="text-align: center;">Powered by <a href="http://cryogenweb.org">Cryogen</a></p></footer>
|
|
</div>
|
|
<script src="https://code.jquery.com/jquery-1.11.0.min.js"></script>
|
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>
|
|
<script src="../../../js/highlight.pack.js" type="application/javascript"></script>
|
|
<script>hljs.initHighlightingOnLoad();</script>
|
|
|
|
<link rel="stylesheet" type="text/css" href="https://storage.googleapis.com/app.klipse.tech/css/codemirror.css">
|
|
<script>
|
|
window.klipse_settings = {
|
|
"selector" : ".klipse-clojure"
|
|
};
|
|
</script>
|
|
<script src="https://storage.googleapis.com/app.klipse.tech/plugin/js/klipse_plugin.js"></script>
|
|
</body>
|
|
</html>
|