Sometimes it's called a closure, other times a saved lexical environment. Or, as some of us like to say, let over lambda.
— Let Over Lambda, Chapter 2: Closures.
λ
When I started to learn Reagent, I noticed a familiar pattern in form-2 components that use a let binding to return a lexical closure, an idiom that Doug Hoyte elegantly calls let over lambda in his book of the same title:
"Let over lambda is a nickname given to a lexical closure. [...] In a let over lambda scenario, the last form returned by a let statement is a lambda expression. It literally looks like let is sitting on top of lambda."
"Sometimes the most valuable thing to have a let form return is an anonymous function which takes advantage of the lexical environment supplied by the let form. To create these functions in lisp we use lambda."
As Hoyte explains, "one way of thinking about closures is that they are functions with state". In Common Lisp, this pattern can be used to create a counter by surrounding a lambda
expression with a let
form:
This expression returns a function that increments the counter
variable every time it is called. In Clojure, anonymous functions can be created using the fn
special form that is analogous to Common Lisp's lambda
:
In Reagent, the simplest form of a let over lambda would create a similar let binding, but using reagent.core/atom
instead of clojure.core/atom
so that Reagent can watch for and re-render the component on state changes:
However, here the inner function would be called only when rendering the component for the first time, but nothing would be able to access or increment the value of counter
and thus trigger the component to re-render.
Let's modify the last snippet to return a Hiccup vector that describes an HTML button, adds an event listener that increments the counter
on every click, and contains a child expression that dereferences the counter
:
This closure will remember the number of times that the button has been clicked. After being initialized with a value of 0
, the value of counter
will be incremented by 1
every time the #(swap! counter inc)
function is called.
Finally, let's turn this button into an actual component with state by using the above way of creating closures with Reagent. But in order to create such closures as needed, we must define a function that returns a let over lambda:
Coincidentally, this pattern used to create Reagent components with state is exactly what Hoyte calls "lambda over let over lambda". That is, form-2 components follow the lambda over let over lambda pattern:
Thus, every time create-counter-button
is called, a new closure will be created to store the state of the new button in it's own lexical environment. It's amazing that this can be accomplished without objects and classes at all!
Let and lambda are fundamental; objects and classes are derivatives. As Steele says, the "object" need not be a primitive notion in programming languages. — Let Over Lambda, Chapter 2.
Conclusion
Much like "let over lambda is a nickname given to a lexical closure", we can say that Let Over Hiccup is a nickname for a closure that returns a Hiccup vector, regardless of how many layers of let over lambdas and nested components there will be in more sophisticated compositions.
I think it's very interesting and surprising that a simple example of a Reagent component uses a pattern that gives its name to an advanced book on Lisp macros. I'm really excited to figure out what other topics about macros can be applied to the seemingly unrelated field of functional components for building user interfaces on the Web.
Links
- Let Over Lambda: 50 Years of Lisp, by Doug Hoyte.
- Reagent: A simple ClojureScript interface to React.
Top comments (0)