user977828
user977828

Reputation: 7679

Why to use *let* in functions?

I do not understand what kind of advantages or disadvantages are in using let, because it is possible to avoid to use let at all like in the code below?

(defn make-adder [x]
  (let [y x]
    (fn [z] (+ y z))))
(def add2 (make-adder 2))

;; or without let
(defn add2 [z] (+ 2 z))

(add2 4) 

Upvotes: 1

Views: 444

Answers (3)

T.Gounelle
T.Gounelle

Reputation: 6033

In most programming languages, you can declare and use local variables that helps you in the design of the function (or method) body. In Clojure, let let you bind local symbols to any expression that can result into a value, a data structure or another local function definition.

Your example is so simple that there is no need for the let construct, but you will soon encounter cases where it will be of great help for the function logic readability.

let is also a way to factorise code, which improve readability and performance by computing an expression only once and use the result in several places.

And, last but not least, let destructuring is very convenient to produce concise code when expressions return compound structures like collections.

Upvotes: 7

Thumbnail
Thumbnail

Reputation: 13473

The issue is not whether to use let, but whether to define your add2 function directly or use a function-making-function, make-adder, to make it.

Your function-making-function is

(defn make-adder [x]
  (let [y x]
    (fn [z] (+ y z))))

As @user1571406 says, the let does nothing useful here, and is better omitted, giving

(defn make-adder [x]
  (fn [z] (+ x z)))

... which is much easier to read. We can see that, given a number, it returns a function that adds that number to whatever it is presented with:

((make-adder 2) 4)
;6

Giving (make-adder 2) a name is just confusing.

Defining it explicitly, a nameless function that adds 2 is

(fn [z] (+ 2 z))

Applying it to 4:

((fn [z] (+ 2 z)) 4)
;6

Upvotes: 2

Magos
Magos

Reputation: 3014

There's three main reasons to name intermediate results:

  1. You want to help programmers (including yourself) understand what they are, down the line.
  2. You want to cache them so they can be used in multiple places without having to evaluate the expression again. This is especially important if evaluating your expression has side effects, and you only want them to happen once.
  3. You're creating a closure which is going to use the let-ed name but not give it out to the outside world.

In your make-adder example, there's no real need for the let because it's just establishing an alias for the incoming parameter. But if you have something a bit more involved then these advantages start becoming relevant.

Just because I have it to hand, here's some code from another recent answer of mine:

(defn trades-chan
  "Open the URL as a tab-separated values stream of trades. 
  Returns a core.async channel of the trades, represented as maps.
  Closes the HTTP stream on channel close!"
  [dump-url]
  (let[stream (-> dump-url
                 (client/get {:as :stream})
                 :body) ;;This is an example of 3. 
       lines  (-> stream
                 io/reader 
                 line-seq) 
       ;; This is an example of 2. I don't want to make multiple readers just because I use them in multiple expressions.

       ;;fields and transducer are examples of 1.
       fields (map keyword (str/split (first lines) #"\t")) 
       transducer (map (comp #(zipmap fields %) #(str/split % #"\t")))

       ;;output-chan is another example of 2  
       output-chan (async/chan 50 transducer)]
    (async/go-loop [my-lines (drop 1 lines)]
                   (if (async/>! output-chan (first my-lines))   
                     (recur (rest my-lines))         
                     (.close stream)))  ;;Here, the closure closes the HTTP stream, so it needs a name to refer to it by.             
    output-chan))

Upvotes: 7

Related Questions