Motivation
A short discussion on the #clojure-berlin channel ( Clojurians on Slack) got me interested in the letfn
special form of clojure.core
. The question was raised if the docstring of letfn
describes it well. Wether it is, or not, I got interested in how it works and more specifically why it’s syntax is different from let
for example.
Here is the documentation for letfn
:
(doc letfn)
-------------------------
clojure.core/letfn
(letfn [fnspecs*] exprs*)
Special Form
fnspec ==> (fname [params*] exprs) or (fname ([params*] exprs)+)
Takes a vector of function specs and a body, and generates a set of
bindings of functions to their names. All of the names are available
in all of the definitions of the functions, as well as the body.
As you can see it takes a vector of function specs and every function spec has the form (fname [params\*] exprs)
. I was quite surprised to see such a syntax for the specs: Each spec is a list, but it’s not quoted. So outside of the letfn
special form the spec would be evaluated.
Why is this implemented this way? I am going to explore this and take this as an opportunity to dive into Clojure special forms, macros and evaluation.
What letfn does
You might wonder why letfn
exists at all. Can’t it be simply replaced by let
? Let’s investigate this a bit:
(let [foo (fn [] "abc")
bar (fn [] "def")]
(str (foo) (bar)))
;; => abcdef
We used let
to define two functions in a local scope. We can also refer from one function to the other:
(let [foo (fn [] "abc")
bar (fn [] (str (foo) "def"))]
(bar))
;; => abcdef
However, the let
bindings are evaluated sequentially, so we cannot refer from the first function to the second:
(let [bar (fn [] (str (foo) "def"))
foo (fn [] "abc")]
(bar))
;; Unable to resolve symbol: foo in this context
In this simple example you could of course just switch the positions of the bindings and define foo
first and it would work just fine, as we saw in a previous example. Using let
is not possible though, if you need to have mutually recursive function, that means each function calling the other, like this:
(let [recursive-even
(fn [n] (if (= n 0)
true
(recursive-odd (dec n))))
recursive-odd
(fn [n] (if (= n 0)
false
(recursive-even (dec n))))]
(recursive-even 7))
;; Unable to resolve symbol: recursive-odd in this context
(based on example in C on Wikipedia)
This will always fail, because each function depends on the other. A common way to resolve this, is inlining one function in the other.
letfn
allows to keep separate functions calling each other:
(letfn [(recursive-even [n]
(if (= n 0)
true
(recursive-odd (dec n))))
(recursive-odd [n]
(if (= n 0)
false
(recursive-even (dec n))))]
(recursive-odd 7))
;; => true
Please pay attention to the different syntax of the bindings with let
and letfn
. In letfn
there is no explicit usage of fn
, instead a simple, unquoted list with the function name as the first element is provided. The other two elements are a vector of function parameters and the function body. In a nutshell, the function specifications resemble the usage of defn
without the optional parameters:
(doc defn)
-------------------------
clojure.core/defn
([name doc-string? attr-map? [params*] prepost-map? body] [name doc-string? attr-map? ([params*] prepost-map? body) + attr-map?])
Macro
Same as (def name (fn [params* ] exprs*)) or (def
name (fn ([params* ] exprs*)+)) with any doc-string or attrs added
to the var metadata. prepost-map defines a map with optional keys
:pre and :post that contain collections of pre or post conditions.
Application of letfn
If you do not need to use mutual recursion in a local scope you should not need to use letfn
at all. In the global scope you can make use of declare
to have the functions names available and thus allowing mutual recursion. In a local scope without mutual recursion you simply can use let
.
Implementation of letfn
Let’s have a look how letfn
is implemented and see if it helps to understand why it uses this syntax.
(source letfn)
(defmacro letfn
"fnspec ==> (fname [params*] exprs) or (fname ([params*] exprs)+)
Takes a vector of function specs and a body, and generates a set of
bindings of functions to their names. All of the names are available
in all of the definitions of the functions, as well as the body."
{:added "1.0", :forms '[(letfn [fnspecs*] exprs*)],
:special-form true, :url nil}
[fnspecs & body]
`(letfn* ~(vec (interleave (map first fnspecs)
(map #(cons `fn %) fnspecs)))
~@body))
Let’s have a look what this pretty simple macro expands to, when applied to our example usage:
(-> '(letfn [(recursive-even [n]
(if (= n 0)
true
(recursive-odd (dec n))))
(recursive-odd [n]
(if (= n 0)
false
(recursive-even (dec n))))]
(recursive-odd 7))
macroexpand-1
clojure.pprint/pprint)
This expands to:
(letfn*
[recursive-even
(clojure.core/fn
recursive-even
[n]
(if (= n 0) true (recursive-odd (dec n))))
recursive-odd
(clojure.core/fn
recursive-odd
[n]
(if (= n 0) false (recursive-even (dec n))))]
(recursive-odd 7))
Okay, that’s interesting. As you can see, this looks like the normal let
binding now. The argument to letfn*
is a normal binding vector, consisting of symbol - expression pairs, just like let
.
The letfn
macro is very simple. The use of the unquoted list in the function specs allows to have a very concise macro with a simple logic.
But what is letfn*
? You won’t even find it in clojure.core
. Well, at least not in the Clojure code. Instead you would need to have a look at the Clojure compiler, written in Java. letfn\*
gets mapped to LetFnExpr
. The compiler than also takes care of handling the mutual recursion and scoping. You can already dig into the code from the last link. I might myself get into this, but that’s the story of another blog post…. ;)
Nils