emacs.d/clones/lisp/clojure-doc.org/articles/language/macros/index.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 &amp;
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 &amp; 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 &amp; 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 &amp; 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
[&amp; 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-&gt;boolean
[val]
`(let [b (= ~val "yes")]
b))
;= #'user/yes-no-&gt;boolean
</code></pre><pre><code class="klipse-clojure nohighlight">(macroexpand-1 '(yes-no-&gt;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-&gt;boolean
[val]
`(let [b# (= ~val "yes")]
b#))
;= #'user/yes-no-&gt;boolean
</code></pre><pre><code class="klipse-clojure nohighlight">(macroexpand-1 '(yes-no-&gt;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=&gt; `if
if
user=&gt; `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=&gt; (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">&laquo; Clojure interoperability with Java</a>
||
<a href="../polymorphism/index.html">Polymorphism in Clojure: Protocols and Multimethods &raquo;</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 &copy; 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>