In (reduce f val coll), is the val an accumulator?
Asked Answered
S

6

11

When you call reduce and pass it a function and two arguments, can the first argument be considered to be an accumulator?

Is it always an accumulator?

Is it sometimes an accumulator?

I was reading a blog entry about using Clojure to parse big files and found this line:

(reduce line-func line-acc (line-seq rdr))

Link to the blog entry:

http://lethain.com/reading-file-in-clojure/

What about a simple: (reduce + [1 2 3])? Is there an accumulator involved?

I take it my question boils do to: "What is exactly an accumulator?"

But I'd still like to understand too the relation between an accumulator and the reduce function. So any answer to these specific (related) questions are most welcome!

Sebastien answered 5/7, 2012 at 16:11 Comment(1)
I'm wrestling with basic functional concepts like 'reduce' also. Thanks for asking this question.Olszewski
D
5

In (reduce f val coll), is the val an accumulator?

No. It's an argument to function f. This means f is applied over val and the first element in coll.

For instance:

(reduce + 1 [2 3 4])        ;; 10
(reduce + (cons 1 [2 3 4])) ;; 10

What about a simple: (reduce + [1 2 3])? Is there an accumulator involved?

No. It's as a series of applications of function f; like this:

(reduce f [1 2 3 4]) ; ==> (f (f (f 1 2) 3) 4)
(reduce f 1 [2 3 4]) ; ==> (f (f (f 1 2) 3) 4)

Notice that in both cases, the inner-most call to f takes parameters 1 and 2 ? In the first case, 1 and 2 are the first and second elements of coll; in the second case, 1 is the lone value and 2 is the first element of coll.

What is exactly an accumulator?

An accumulator is a variable that holds intermediate results of a computation. Like in this snippet of Java:

int sum = 0;
for (int i = 0; i < 10; i++) {
    sum += i;
}
return sum;

Here, the value of variable sum is changed as the loop progresses. In Clojure, variables are immutable, so you do not see this idiom. Instead, the accumulator is more often (but not always) a parameter to a recursive function.

For instance, here's a function which reverses a list by "accumulating" the first entry in the list into the front of an accumulator. In this case, the variable is not changed, but is passed to another call to the function.

(defn reverse [[f & r] acc]
  (if (nil? f)
    acc
    (recur r (conj acc f))))

(reverse [1 2 3] ()) ;; [3 2 1]
Downstage answered 5/7, 2012 at 16:31 Comment(5)
Your answer is contradicting. In recursive call you say that it is accumulator and reduce is just a higher order function that use recursive call hence the val is an accumulatorBeefy
There is an accumulator involved in reduce and it's the first parameter of the function f. val, if present, is its initial value.Vitta
No contradictions here. In function reverse, you need to supply an accumulator. In function reduce, you do not; if the internal implementation uses one, that's an implementation detail, hidden from the user. It could just as well make a recursive call with an expression, without using a variable.Downstage
That depends on you thinking "accumulator" always means "a mutable variable that is updated in a loop". The whole point of reduce is to let you do accumulation in a functional and immutable way. So it would be correct to say that reduce uses a functional accumulator.Inbreeding
In addition, the only reason why you don't always need to supply an initial value to reduce is because reduce will use the first element of the sequence as the initial value. This is well specified and is not an implementation detail: with reduce there must always be an initial value, and inside the transformation function, the first argument is always the accumulated value, whether you supply the value to reduce or not.Inbreeding
L
5

It can be an accumulator.

It depends on how you use it, and also on your definition of "accumulator".

Here's a traditional, mutable accumulator, note the need to continue to pass the same accumulator on at each step:

(reduce 
  (fn [atom val] (do (swap! atom + val) atom))
  (atom 10)
  [1 2 3 4 5])
=> #<Atom@115872f5: 25>

Here's reduce being used with an immutable "accumulator". Although accumulators are traditionally mutable, I think most functional programmers would define this as an accumulator:

(reduce + 10 [1 2 3 4 5])
=> 25

Here's a reduce where you don't accumulate anything, so it's hard to make a case that the second argument is an accumulator:

(reduce 
  (fn [_ x] (println x))
  nil 
  [1 2 3])
Levi answered 6/7, 2012 at 10:59 Comment(0)
S
4

I am assuming the original question is using accumulator as a generic term, not an official term used in a language.

I do not know if the first argument after the function (the second argument) would be called an accumulator in Clojure's terms. But it certainly seems to act that way.

In the following:

(defn reduce-csv-row
     "Accepts a csv-row (a vector) a list of columns to extract, 
     and reduces the csv-row to a smaller list based on selection
     using the values in col-nums (a vector of integer vector 
     positions.)"

    [csv-row col-nums]

    (reduce
        (fn [filter-csv-row col-num]

            ;Don't use vectors without the proper information in them.

            (if-not (<= (count csv-row) 1)
                (conj filter-csv-row (nth csv-row col-num nil))))
        []
        col-nums))

I certainly expect a sequence returned after calling this function, so accumulator might not be a bad term, but as an official term, I cannot say.

Spindle answered 5/7, 2012 at 16:33 Comment(0)
B
2

Is it always an accumulator?

Yes it is always an accumulator. Accumulator is something that holds the intermediate values of a computation as it progress and when the computation is over the accumulator has the final result of the computation. Whether the accumulator is a mutable or immutable that is a different aspect of an accumulator, but this is what an accumulator is conceptually.

Is it sometimes an accumulator?

No, it is always an accumulator in a reduce, because the whole concept of reduce AKA fold is to covert a list of values into single value and you do need an accumulator for doing such computations if the processing of the next element in list need the result of processing the previous element of the list and so on.

When you don't pass an accumulator initial value (i.e the val part in the reduce function signature) then the initial value of the accumulator is set to first element of the list and processing will start from the second element of the list.

Treating the val as the first argument to f is conceptually incorrect because if this is the case then f should always get the same val that was specified initially, this is like creating the partial function of f with first param as val. Each call to f will get val as the previous return value of call to f. Hence val is an accumulator.

Beefy answered 6/7, 2012 at 4:15 Comment(0)
I
0

Ankur's answer is correct. Also, I think this link explains things very well:

http://www.learningclojure.com/2010/08/reduce-not-scary.html

Now, to answer your questions...


Yes. The second argument to reduce is the initial value of the accumulator.


Yes, it is always an accumulator. The whole point of reduce is that it lets you do accumulation in a functional and immutable way.

I will note that it is possible to use reduce in a way where the accumulation does not matter, like in mikera's answer. In that case, reduce is still doing the accumulation (internally), but it uses the same value over and over again, so it has no noticeable effect.


When calling reduce with only two arguments... the rules that Clojure uses are a little complex, but what it boils down to is that this...

(reduce + [1 2 3])

...will use the first element of the sequence as the initial value, which means it's the same as this:

(reduce + 1 [2 3])

You asked about what an accumulator is. An accumulator is the concept of accumulating data while looping over something.

In imperative languages, an accumulator is generally a variable that is mutated while looping. Let's look at Leonel's example, slightly modified:

var sum = 0;
for (var i = 0; i < 10; ++i) {
    sum = sum + i;
}
return sum;

At first, this would seem to be impossible to do in a functional way. But with reduce, you can!

(reduce (fn [sum i] (+ sum i)) 0 (range 0 10))

How this works is, reduce takes three arguments:

  1. A transformation function
  2. The initial value of the accumulator
  3. A sequence

It calls the transformation function with two arguments:

  1. sum is the current value of the accumulator
  2. i is the current element of the sequence

Now, whatever the transformation function returns is used as the current value of the accumulator. In other words... on the first iteration sum will be the initial value. After the first iteration, sum is whatever the transformation function returned on the previous iteration.

Perhaps it will help if I write an implementation of reduce in JavaScript that uses mutation:

function reduce(f, init, coll) {
    for (var i = 0; i < coll.length; ++i) {
        init = f(init, coll[i]);
    }
    return init;
}

reduce(function (sum, i) { return sum + i }, 0, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);

As you can see, the reduce function looks very similar to the imperative code from before.

Now let's implement reduce in a functional way, without mutation, in Clojure:

(defn reduce [f init coll]
  (if-let [s (seq coll)]
    (reduce f (f init (first s)) (rest s))
    init))

(reduce (fn [sum i] (+ sum i)) 0 (range 0 10))

But regardless of whether reduce does the accumulation in a mutable or immutable way, it is doing an accumulation.


For funsies, it's interesting to note that Clojure implements reverse using reduce:

(defn reverse
  "Returns a seq of the items in coll in reverse order. Not lazy."
  {:added "1.0"
   :static true}
  [coll]
    (reduce1 conj () coll))

Figuring out why this works is an interesting mental exercise.

You can also do neat stuff like implementing map, filter, and other stuff using reduce. But I think that goes a bit beyond your question.

Inbreeding answered 18/4, 2014 at 7:11 Comment(0)
Y
0

In

(reduce f x y)
  • Is x always an accumulator? No.
  • Is x sometimes an accumulator? No

  • x is the initial value fed to the reduce.

What is exactly an accumulator?

An accumulator is a local binding that is returned as the value of a recursive function.

For example, if we were to implement reduce ourselves, we might do so as follows:

(defn reduce [f acc coll]
  (if (empty? coll)
    acc
    (recur f (f acc (first coll)) (rest coll))))
  • acc is an accumulator: Being an argument, it's a local.
  • It is returned as the value of the function when coll is empty.

Whether something is an accumulator depends upon the body of the function.

For example, if we implement reverse this way:

(defn reverse [coll]
  (loop [in coll, out ()]
    (if (seq in)
      (recur (rest in) (cons (first in) out))
      out)))

.. . then out is an accumulator.

But if we implement it this way:

(defn reverse [coll]
  (reduce conj () coll))

... there is no accumulator.

Inside the reduce call, acc is initially bound to (). But it makes no sense to say that () is an accumulator.

Yetty answered 5/9, 2014 at 7:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.