16

Consider the following macro:

(defmacro somemacro []
    (list 'let ['somevar "Value"] 'somevar))

Expanding it yields the following result:

(macroexpand '(somemacro))

Result:

(let* [somevar "Value"] somevar)

I have two questions about let* (with the asterisk):

  • What does it mean? (In particular: is it documented somewhere?)
  • Why is the macro not expanded with the 'normal' let? (I.e., let without the asterisk.) Both yield the same result (in my experimentation). Is there a counter example?

Unluckily I could not find any 'official' documentation about let*, that's why I'm asking here.

Sources I've already considered:

(doc let*)  ; --> nil
(source let*)  ; --> source not found
  1. https://clojuredocs.org/clojure.core --> I see not let* here (although there is e.g. list*)
  2. https://clojuredocs.org/clojure.core/let --> only mentioned once in a comment, that is not totally clear to me:

    Nota Bene: let in Clojure is like let* in Scheme -- each init-expr has access to the preceding binding forms. (There is also a let*, but it is more or less let without destructuring, and in fact is the underlying implementation.)

  3. LET versus LET* in Common Lisp --> this question is about common lisp, but maybe it's the same in Clojure?
  4. This answer: https://stackoverflow.com/a/5084339/3398271

    In Clojure it basically means "foo* is like foo, but somehow different, and you probably want foo". In other words, it means that the author of that code couldn't come up with a better name for the second function, so they just slapped a star on it.

--> Is this the case for let and let*? But if so, still the question remains, what is exactly the difference?

  1. What is the difference between let and let* in Scheme? --> Is this the same in Clojure?
Community
  • 1
  • 1
Attilio
  • 1,624
  • 1
  • 17
  • 27
  • There is apparently also an `if` and `if*` pair. As with `let`, documentation is not easy to find. I am not aware that `if` destructures, so something else must be going on. – Reb.Cabin Dec 29 '15 at 23:40
  • @Reb.Cabin,Interesting. With `if*` not being documented it probably is internal and not meant for consumption. I looked at the Clojure source on GitHub, but GitHub's search stripped `*` out. There are lots of cases of `if`. I suppose the answer is to download the source locally and run `grep` locally. – Shannon Severance Dec 30 '15 at 17:23
  • Clojure has no equivalent of Scheme's `let`. Clojure's `let` has the semantics of Scheme's `let*`. – DaoWen Jan 28 '21 at 15:16

2 Answers2

21

let* is an internal implementation detail. let is a macro implemented in terms of let*. https://github.com/clojure/clojure/blob/clojure-1.7.0/src/clj/clojure/core.clj#L4301

The macro let adds parameter destructuring to let*. This is the standard pattern for xyz and xyz* in Clojure, with the * version not being documented. An exception being list and list*.

Shannon Severance
  • 18,025
  • 3
  • 46
  • 67
  • 1
    Thanks for the explanation, I tried it out and, in fact `(let [[a b c & d :as e] [1 2 3 4 5 6 7]] [a b c d e])` works, whilst `(let* [[a b c & d :as e] [1 2 3 4 5 6 7]] [a b c d e])` causes a CompilerException. – Attilio Jul 28 '15 at 18:02
  • 1
    I might not have been clear. `let*` does not have destructuring. The `let` macro calls `let*`, providing destructuring in the process. – Shannon Severance Jul 28 '15 at 18:05
  • Yes, I fully understood it (what I meant, is that I found an example which proves exactly this :) ) Sorry for the confusion. – Attilio Jul 28 '15 at 18:10
1

I thought I would add that the reason why macroexpand returns let* instead of let can be found in the documentation of macroexpand:

Repeatedly calls macroexpand-1 on form until it no longer represents a macro form, then returns it.

So what happens is the first call of macroexpand-1 returns (let [somevar "Value"] somevar), and the second expands let into let*.

Indeed,

user=> (println (clojure.string/join "\n" (take 3 (iterate macroexpand-1 '(somemacro)))))
(somemacro)
(let [somevar "Value"] somevar)
(let* [somevar "Value"] somevar)
nil

If you were to use destructuring in your macro, the output would be more interesting:

user=> (defmacro destructuring-macro [] `(let [[x y z] [:x :y :z]] y))
#'user/destructuring-macro

user=> (println (clojure.string/join "\n" (take 3 (iterate macroexpand-1 '(destructuring-macro)))))
(destructuring-macro)
(clojure.core/let [[testing.core/x testing.core/y testing.core/z] [:x :y :z]] testing.core/y)
(let* [vec__8356 [:x :y :z] x (clojure.core/nth vec__8356 0 nil) y (clojure.core/nth vec__8356 1 nil) z (clojure.core/nth vec__8356 2 nil)] testing.core/y)
nil

Notice that let is fully qualified by the syntax quote, because it is not a special form (even though its documentation says it is). The underlying special form is let*, which is not fully qualified by the syntax quote.

Resigned June 2023
  • 4,638
  • 3
  • 38
  • 49