let

added
1.0

ns
clojure.core

type
macro

(let bindings & body)

binding => binding-form init-expr

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.

                ;; 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]
            
                ;;defina 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)