1

I was writing the code that does same thing as 'reduce' function in clojure ex) (reduce + [1 2 3 4]) = (+ (+ (+ 1 2) 3) 4).

(defn new-reduce [fn coll]
  (def answer (get coll 0))
  (loop [i 1]
    (when (< i (count coll))
      (def answer (fn answer (get coll i)))
    (recur (inc i))))
answer)

In my code I used the global variable, and for me it was easier for me to understand that way. Apparently, people saying it is better to change the global variable to local variable such as let. So I tried..

(defn new-reduce [fn coll]
  (let [answer (get coll 0)] 
   (loop [i 1]
     (when (< i (count coll))
      (fn answer (get coll i))
     (recur (inc i))))))

To be honest, I am not really familiar with let function and even though I try really simple code, it did not work. Can somebody help me to fix this code and help me to understand how the let (local variables) really work ? Thank you. (p.s. really simple code that has loop inside let function will be great also).

1
  • Any time you have a loop inside of a let, the let bindings will remain constant through all iterations of the loop. Remember that let bindings are bindings, not variables. Commented Feb 5, 2016 at 1:10

2 Answers 2

1

Let does not create local "variables", it gives names to values, and does not let you change them after giving them the name. So introducing a let is more like defining a local constant.

First I'll just add another item into the loop expression to store the value so far. Each time through the loop we will update this to incorporate the new information. This pattern is very common. I also needed to add a new argument to the function to hold the initial state (reduce as a concept needs this)

user> (defn new-reduce [function initial-value coll]
        (loop [i 0 
               answer-so-far initial-value]
          (if (< i (count coll))
            (recur (inc i) (function answer-so-far (get coll i)))
            answer-so-far)))

user> (new-reduce + 0 [1 2 3])
6

This moves the "global variable" into a name that is local to the loop expression can be updated once per loop at the time you jump back up to the top. Once it reaches the end of the loop it will return the answer thus far as the return value of the function rather than recurring again. Building your own reduce function is a great way to build understanding on how to use reduce effectively.

There is a function that introduces true local variables, though it is very nearly never used in Clojure code. It's only really used in the runtime bootstap code. If you are really curious read up on binding very carefully.

Sign up to request clarification or add additional context in comments.

2 Comments

Arthur, would it be better to use a parameter name other than fn, since that's the name of a macro? OP used it, but s/he's learning. (fn answer-so-far ...) looks like a syntactically incorrect function definition, i.e. it should be (fn [answer-so-far] ...) or something else. That wouldn't make sense in the context, but the use of fn as a parameter threw me for a loop ... for a moment.
yes, that's correct. In presenting the closes thing to D. K.'s example I didn't point out that this is strange (and it had me confused for the first draft of the answer) I'll change it to "function"
0

Here's a simple, functional solution that replicates the behavior of the standard reduce:

(defn reduce
  ([f [head & tail :as coll]]
   (if (empty? coll)
     (f)
     (reduce f head tail)))
  ([f init [head & tail :as coll]]
   (cond
     (reduced? init) @init
     (empty? coll) init
     :else (recur f (f init head) tail))))

There is no loop here, because the function itself serves as the recursion point. I personally find it easier to think about this recursively, but since we're using tail recursion with recur, you can think about it imperatively/iteratively as well:

  1. If init is a signal to return early then return its value, otherwise go to step 2
  2. If coll is empty then return init, otherwise go to step 3
  3. Set init to the result of calling f with init and the first item of coll as arguments
  4. Set coll to a sequence of all items in coll except the first one
  5. Go to step 1

Actually, under the hood (with tail-call optimization and such), that's essentially what's really going on. I'd encourage you to compare these two expressions of the same solution to get a better idea of how to go about solving these sorts of problems in Clojure.

3 Comments

Perhaps clearer as(if (or (reduced? init) (empty? coll)) init (recur f (f init head) tail)).
@Thumbnail No, that's not correct (try some of the examples here). You need to dereference the Reduced object.
@Thumbnail No worries :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.