ClojureDocs

Nav

Namespaces

let

clojure.core

Available since 1.0 (source)
  • (let [bindings*] exprs*)
binding => binding-form init-expr
binding-form => name, or destructuring-form
destructuring-form => map-destructure-form, or seq-destructure-form
 Evaluates the exprs in a lexical context in which the symbols in
the binding-forms are bound to their respective init-exprs or parts
therein.
 See https://clojure.org/reference/special_forms#binding-forms for
more information about destructuring.
11 Examples
;; let is a Clojure special form, a fundamental building block of the language.
;;
;; In addition to parameters passed to functions, let provides a way to create
;; lexical bindings of data structures to symbols. The binding, and therefore 
;; the ability to resolve the binding, is available only within the lexical 
;; context of the let. 
;; 
;; let uses pairs in a vector for each binding you'd like to make and the value 
;; of the let is the value of the last expression to be evaluated. let also 
;; allows for destructuring which is a way to bind symbols to only part of a 
;; collection.

;; A basic use for a let:
user=> (let [x 1] 
         x)
1

;; Note that the binding for the symbol y won't exist outside of the let:
user=> (let [y 1] 
         y)
1
user=> (prn y)
java.lang.Exception: Unable to resolve symbol: y in this context (NO_SOURCE_FILE:7)

;; Note that if you use def inside a let block, your interned variable is within 
;; the current namespace and will appear OUTSIDE of the let block. 
user=> (let [y 1] 
         (def z y) 
         y)
1
user=> z
1

;; Another valid use of let:
user=> (let [a 1 b 2] 
         (+ a b))
3

;; The forms in the vector can be more complex (this example also uses
;; the thread macro):
user=> (let [c (+ 1 2)
             [d e] [5 6]] 
         (-> (+ d e) (- c)))
8

;; The bindings for let need not match up (note the result is a numeric
;; type called a ratio):
user=> (let [[g h] [1 2 3]] 
         (/ g h))
1/2

;; From http://clojure-examples.appspot.com/clojure.core/let with permission.
user=> (let [a (take 5 (range))
             {:keys [b c d] :or {d 10 b 20 c 30}} {:c 50 :d 100}
             [e f g & h] ["a" "b" "c" "d" "e"]
             _ (println "I was here!")
             foo 12
             bar (+ foo 100)]
         [a b c d e f g h foo bar])
I was here!
[(0 1 2 3 4) 20 50 100 "a" "b" "c" ("d" "e") 12 112]
; :as example 

user=> (let [[x y :as my-point] [5 3]]
         (println x y)
         (println my-point))

5 3
[5 3]

; :as names the group you just destructured.

; equivalent to (and better than)

user=> (let [[x y] [5 3]
             my-point [x y]]
         ;...
;;; map destructuring, all features
user=>
(let [
      ;;Binding Map
      {:keys [k1 k2]        ;; bind vals with keyword keys
       :strs [s1 s2]        ;; bind vals with string keys
       :syms [sym1 sym2]    ;; bind vals with symbol keys
       :or {k2 :default-kw, ;; default values
            s2 :default-s, 
            sym2 :default-sym} 
       :as m}  ;; bind the entire map to `m`
      ;;Data
      {:k1 :keyword1, :k2 :keyword2,  ;; keyword keys
       "s1" :string1, "s2" :string2,  ;; string keys
       'sym1 :symbol1,                ;; symbol keys
       ;; 'sym2 :symbol2              ;; `sym2` will get default value
       }] 
  [k1 k2 s1 s2 sym1 sym2 m])  ;; return value

[:keyword1, :keyword2, 
 :string1, :string2,
 :symbol1, :default-sym, ;; key didn't exist, so got the default
 {'sym1 :symbol1, :k1 :keyword1, :k2 :keyword2, 
  "s1" :string1, "s2" :string2}]

;; remember that vector and map destructuring can also be used with 
;; other macros that bind variables, e.g. `for` and `doseq`
;;; no value of a key
user> (let [{:keys [a b] :as m} (:x {})]
        [a b m])
[nil nil nil]

;;; same as above
user> (let [{:keys [a b] :as m} nil]
        [a b m])
[nil nil nil]

;;; similar case on Vector
user> (let [[a b :as v] nil]
        [a b v])
[nil nil nil]
;; lexical clojure (or let-over-fn) is an idiom for doing, in functional languages,
;; something very similar to object based programming.
;; Using combinations of 'let' and 'fn' can produce many interesting results.

;; note the use of the ! on the functions to indicate the side effect
(defn counter []
  (let [cnt (atom 0)]
    {:inc! (fn [] (swap! cnt inc))
     :dec! (fn [] (swap! cnt dec)) 
     :get (fn [] @cnt)} ))

;; we can now make and use the object
(let [cnt (counter)]
  ((:inc! cnt))
  ((:inc! cnt)) 
  ((:get cnt)))
;;=> 2
(let [[a b & c :as d] [1 2 3 4 5]]
   (println a) ; 1
   (println b) ; 2
   (println c)  ; (3 4 5)
   d) ;[1 2 3 4 5]
;;define F1Car record
(defrecord F1Car [team engine tyre oil])

;;build the constructor distructing a single map with options
(defn make-f1team [f1-team f1-engine {:keys [f1-tyre f1-oil] :as opts}]
  (let [{:keys [tyre oil]} opts]
    (map->F1Car {:team f1-team
                       :engine f1-engine
                       :tyre f1-tyre
                       :oil f1-oil})))

;;create a record
(def mclaren (make-f1team "RedBull" "Renault" {:f1-tyre"Pirelli" :f1-oil "Castrol"}))

;;retrieve values
(keys mclaren)
(vals mclaren)
(:team mclaren)
(:oil mclaren)
;;It is possible to enumerate the symbols created within a let using a macro.
(defmacro local-context []
  (let [symbols (keys &env)]
    (zipmap (map (fn [sym] `(quote ~sym)) symbols) symbols)))

(let [a :b lc (local-context)] lc)
;; => {a :b}
;; the destructuring key, k in this example, doesn't have to be a literal keyword
(let [k (keyword "name")
      {person-name k} {:name "john"}]
  person-name)
;; => "john"
;; There is some confusion in various lisps about the scope of variables in the
;; bindings of let variables.  It works as follows in Clojure.
(let [y 'OUTER]
  (let [y 'INNER
        x y] ;; binds x to 'INNER, not to 'OUTER
    x))
;; => inner

;; Note that both Common Lisp and emacs lisp would bind x to 'OUTER not 'INNER
;; in the corresponding code.

;; But this is because `let` in Common Lisp and emacs lisp does not bind sequentially.
;; The correct analogue of Clojure's `let` is in fact `let*` in these other lisps,
;; in which case the behaviour does match.
See Also

fnspec ==> (fname [params*] exprs) or (fname ([params*] exprs)+) Takes a vector of function specs...

Added by gstamp

bindings => binding-form test If test is true, evaluates then with binding-form bound to the valu...

Added by Chort409

params => positional-params*, or positional-params* & rest-param positional-param => binding-form ...

Added by phreed

List comprehension. Takes a vector of one or more binding-form/collection-expr pairs, each follow...

Added by MicahElliott
2 Notes
    By , created 14.2 years ago

    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.)

    By , created 6.0 years ago, updated 6.0 years ago

    Be aware that let’s :or always evaluates the default value, unlike the or macro:

    user=> (let [{:keys [a]
                  :or {a (do (println "toto") 1)}} {}]
             a)
    toto
    1
    
    user=> (let [{:keys [a]
                  :or {a (do (println "toto") 1)}} {:a 2}]
             a)
    toto
    2