I find I very rarely use let
in Clojure. For some reason I took a dislike to it when I started learning and have avoided using it ever since. It feels like the flow has stopped when let
comes along. I was wondering, do you think we could do without it altogether ?

- 4,565
- 2
- 28
- 43
-
2Maybe you'll find [this article](http://onclojure.com/2009/03/05/a-monad-tutorial-for-clojure-programmers-part-1/) interesting which covers using functions instead of `let` to explain monads. – sloth Nov 29 '12 at 15:24
6 Answers
Let offers a few benefits. First, it allows value binding in a functional context. Second, it confers readability benefits. So while technically, one could do away with it (in the sense that you could still program without it), the language would be impoverished without a valuable tool.
One of the nice things about let is that it helps formalize a common (mathematical) way of specifying a computation, in which you introduce convenient bindings and then a simplified formula as a result. It's clear the bindings only apply to that "scope" and it's tie in with a more mathematical formulation is useful, especially for more functional programmers.
It's not a coincidence that let blocks occur in other languages like Haskell.

- 3,822
- 1
- 16
- 23
You can replace any occurrence of (let [a1 b1 a2 b2...] ...)
by ((fn [a1 a2 ...] ...) b1 b2 ...)
so yes, we could. I am using let a lot though, and I'd rather not do without it.

- 14,902
- 5
- 47
- 92
-
4In fact `(let [a1 b1 a2 b2] ...)` is better explained by `(((fn [a1] (fn [a2] ...)) b1) b2)`. What I mean is that you can refer to previous bindings in subsequent ones. _Edit:_ parentheses, we all love them. – Jan Nov 28 '12 at 23:52
-
@Jan I guess, but that would have meant that I'd have had to write more parentheses. We can't have that now, can we? – Cubic Nov 28 '12 at 23:54
-
-
2in fact, I think this answer gets to the problem: maybe every use of let is an opportunity to factor out another function ? maybe it's a sign that there's too much complexity in that part of the code ? – Hendekagon Nov 29 '12 at 05:56
-
3@Hendekagon Certainly you could refactor every single let in your code away. I tend to use it for very localized situations though, and I'd rather clutter up one function a bit than clutter up the rest of the program with one line functions I really only need in one place. – Cubic Nov 29 '12 at 06:55
Let is indispensable to me in preventing multiple execution in macros:
(defmacro print-and-run [s-exp]
`(do (println "running " (quote ~s-exp) "produced " ~s-exp)
s-exp))
would run s-exp twice, which is not what we want:
(defmacro print-and-run [s-exp]
`(let [result# s-exp]
(do (println "running " (quote ~s-exp) "produced " result#)
result#))
fixes this by binding the result of the expression to a name and referring to that result twice.
because the macro is returning an expression that will become part of another expression (macros are function that produce s-expressions) they need to produce local bindings to prevent multiple execution and avoid symbol capture.

- 89,153
- 8
- 140
- 205

- 90,827
- 27
- 201
- 284
-
for this kind of situation I tend to add multiple alternative argument lists to my function: (defn f ([x] (f x (dosomthing x))) ([x y] ...), don't know if that's possible with a macro though as I've never written a macro! – Hendekagon Nov 29 '12 at 06:09
I think I understand your question. Correct me if it's wrong. Some times "let" is used for imperative programming style. For example,
... (let [x (...)
y (...x...)
z (...x...y...)
....x...y...z...] ...
This pattern comes from imperative languages:
... { x = ...;
y = ...x...;
...x...y...;} ...
You avoid this style and that's why you also avoid "let", don't you?
In some problems imperative style reduces amount of code. Furthermore, some times It's more efficient to write in java or c. Also in some cases "let" just holds values of subexpressions regardless of evaluation order. For example,
(... (let [a (...)
b (...)...]
(...a...b...a...b...) ;; still fp style

- 3,752
- 1
- 18
- 16
-
-
1
-
I feel like you are saying that stuff like `(let [a1 b1 a2 b2] (someexpression a1 a2))` was somewhat imperative. I would not agree with that. Since let is somewhat equivalent of `(let [a1 b1] ...) by ((fn [a1] ...) b1)` (see cubics answer) it should be clear that let is a functional concept. You seem to invoke the "serial evalutation" definition of "imperative" programming, but let is as serial invoking as nested function calls are. Of course I see the point that people new to FP tend to have a let-heavy style. – wirrbel Apr 04 '13 at 06:20
There are at least two important use cases for let
-bindings:
First, using let
properly can make your code clearer and shorter. If you have an expression that you use more than once, binding it in a let
is very nice. Here's a portion of the standard function map
that uses let
:
...
(let [s1 (seq c1) s2 (seq c2)]
(when (and s1 s2)
(cons (f (first s1) (first s2))
(map f (rest s1) (rest s2)))))))
...
Even if you use an expression only once, it can still be helpful (to future readers of the code) to give it a semantically meaningful name.
Second, as Arthur mentioned, if you want to use the value of an expression more than once, but only want it evaluated once, you can't simply type out the entire expression twice: you need some kind of binding. This would be merely wasteful if you have a pure expression:
user=> (* (+ 3 2) (+ 3 2))
25
but actually changes the meaning of the program if the expression has side-effects:
user=> (* (+ 3 (do (println "hi") 2))
(+ 3 (do (println "hi") 2)))
hi
hi
25
user=> (let [x (+ 3 (do (println "hi") 2))]
(* x x))
hi
25

- 48,199
- 22
- 128
- 192
Stumbled upon this recently so ran some timings:
(testing "Repeat vs Let vs Fn"
(let [start (System/currentTimeMillis)]
(dotimes [x 1000000]
(* (+ 3 2) (+ 3 2)))
(prn (- (System/currentTimeMillis) start)))
(let [start (System/currentTimeMillis)
n (+ 3 2)]
(dotimes [x 1000000]
(* n n))
(prn (- (System/currentTimeMillis) start)))
(let [start (System/currentTimeMillis)]
(dotimes [x 1000000]
((fn [x] (* x x)) (+ 3 2)))
(prn (- (System/currentTimeMillis) start)))))
Output
Testing Repeat vs Let vs Fn
116
18
60
'let' wins over 'pure' functional.

- 31
- 1
- 3
-
Can't trust those timings unfortunately - to profile in Clojure need to use [Criterium](https://github.com/hugoduncan/criterium/) – Hendekagon Aug 27 '14 at 05:23