In (уменьшить f val coll), является ли накопителем?

Когда вы вызываете сокращение и передаете ему функцию и два аргумента, может ли первый аргумент считаться аккумулятором?

Это всегда аккумулятор?

Это иногда аккумулятор?

Я читал запись в блоге об использовании Clojure для синтаксического анализа больших файлов и нашел эту строку:

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

Ссылка на запись в блоге:

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

Как насчет простого: (уменьшить + [1 2 3])? Есть ли накопитель?

Я понимаю, что мой вопрос кипит: "Что такое аккумулятор?"

Но я все равно хотел бы понять также связь между аккумулятором и функцией уменьшения. Поэтому любой ответ на эти конкретные (связанные) вопросы приветствуются!

Ответ 1

In (уменьшить f val coll), является ли накопителем?

Нет. Это аргумент функции f. Это означает, что f применяется над val и первым элементом в coll.

Например:

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

Как насчет простого: (уменьшить + [1 2 3])? Есть ли накопитель?

Нет. Это как серия приложений функции f; например:

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

Обратите внимание, что в обоих случаях самый внутренний вызов f принимает параметры 1 и 2? В первом случае 1 и 2 являются первым и вторым элементами из coll; во втором случае 1 - одиночное значение, а 2 - первый элемент coll.

Что такое аккумулятор?

Аккумулятор представляет собой переменную , которая содержит промежуточные результаты вычисления. Как в этом фрагменте Java:

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

Здесь значение переменной sum изменяется при прохождении цикла. В Clojure переменные неизменяемы, поэтому вы не видите эту идиому. Вместо этого аккумулятор чаще (но не всегда) является параметром для рекурсивной функции.

Например, здесь функция, которая меняет список, "накапливая" первую запись в списке в передней части аккумулятора. В этом случае переменная не изменяется, а передается другому вызову функции.

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

(reverse [1 2 3] ()) ;; [3 2 1]

Ответ 2

Я предполагаю, что исходный вопрос заключается в использовании аккумулятора как общего термина, а не официального термина, используемого на языке.

Я не знаю, будет ли первый аргумент после функции (второй аргумент) называться аккумулятором в терминах Clojure. Но, похоже, это так.

В следующем:

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

Я, конечно же, ожидаю, что после вызова этой функции будет возвращена последовательность, поэтому накопитель может не быть плохим термином, но, как официальный термин, я не могу сказать.

Ответ 3

Это может быть аккумулятор.

Это зависит от того, как вы его используете, а также от определения "аккумулятора".

Здесь традиционный, изменяемый накопитель, обратите внимание на необходимость продолжать передавать один и тот же аккумулятор на каждом шаге:

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

Здесь сокращение используется с неизменным "аккумулятором". Хотя аккумуляторы традиционно изменяемы, я думаю, что большинство функциональных программистов определяли бы это как аккумулятор:

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

Здесь сокращение, в котором вы ничего не накапливаете, поэтому трудно сделать случай, когда второй аргумент является аккумулятором:

(reduce 
  (fn [_ x] (println x))
  nil 
  [1 2 3])

Ответ 4

Это всегда аккумулятор?

Да, это всегда аккумулятор. Аккумулятор - это то, что содержит промежуточные значения вычисления при его прогрессе, и когда вычисление завершено, суммарный сумматор имеет окончательный результат вычисления. Независимо от того, является ли аккумулятор изменчивым или неизменным, что является другим аспектом аккумулятора, но это то, что представляет собой аккумулятор.

Это иногда аккумулятор?

Нет, он всегда является аккумулятором в reduce, потому что вся концепция reduce AKA fold заключается в том, чтобы скрывать список значений в одно значение, и вам нужен аккумулятор для выполнения таких вычислений, если для обработки следующего элемента в списке нужен результат обработки предыдущего элемента списка и т.д.

Если вы не передадите начальное значение аккумулятора (т.е. часть val в сигнатуре функции reduce), тогда начальное значение аккумулятора будет установлено на первый элемент списка, и обработка начнется со второго элемент списка.

Рассмотрение val в качестве первого аргумента f концептуально неверно, потому что, если это так, тогда f всегда должен иметь тот же самый val, который был указан вначале, это похоже на создание частичной функции f с первым параметром val. Каждый вызов f получит val как предыдущее возвращаемое значение вызова f. Следовательно, val является аккумулятором.

Ответ 5

Ответ Ankur правильный. Кроме того, я думаю, что эта ссылка очень хорошо объясняет:

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

Теперь, чтобы ответить на ваши вопросы...


Да. Второй аргумент reduce - это начальное значение аккумулятора.


Да, это всегда аккумулятор. Вся точка reduce заключается в том, что она позволяет выполнять накопление функциональным и неизменным способом.

Отмечу, что можно использовать reduce таким образом, чтобы накопление не имело значения, как в ответ mikera. В этом случае reduce все еще выполняет накопление (внутренне), но он использует одно и то же значение снова и снова, поэтому он не имеет заметного эффекта.


При вызове reduce только с двумя аргументами правила, которые Clojure использует, немного сложны, но то, что он кипит вплоть до того, что это...

(reduce + [1 2 3])

... будет использовать первый элемент последовательности в качестве начального значения, что означает, что он такой же:

(reduce + 1 [2 3])

Ты спросил, что такое аккумулятор. Аккумулятор представляет собой концепцию аккумулирования данных при переходе через что-то.

В императивных языках аккумулятор обычно является переменной, которая мутируется во время цикла. Давайте посмотрим на пример Леонеля, слегка измененный:

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

Сначала это было бы невозможно сделать функциональным способом. Но с reduce вы можете!

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

Как это работает, reduce принимает три аргумента:

  • Функция преобразования
  • Начальное значение аккумулятора
  • Последовательность

Он вызывает функцию преобразования с двумя аргументами:

  • sum - текущее значение аккумулятора
  • i - текущий элемент последовательности

Теперь, независимо от того, какая функция преобразования возвращается, используется как текущее значение аккумулятора. Другими словами... на первой итерации sum будет начальное значение. После первой итерации sum есть функция преобразования, возвращаемая на предыдущей итерации.

Возможно, это поможет, если я напишу реализацию reduce в JavaScript, которая использует мутацию:

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

Как вы можете видеть, функция reduce выглядит очень похожей на императивный код ранее.

Теперь давайте реализовать reduce функционально, без мутации, в 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))

Но независимо от того, имеет ли reduce накопление в изменяемом или неизменяемом способе, он выполняет накопление.


Для funsies интересно отметить, что Clojure реализует reverse с помощью 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))

Выяснение того, почему это работает, является интересным умственным упражнением.

Вы также можете делать аккуратные вещи, такие как реализация карты, фильтра и других вещей, используя сокращение. Но я думаю, что это немного выходит за рамки вашего вопроса.

Ответ 6

В

(reduce f x y)
  • Является ли x всегда аккумулятором? Нет
  • Является ли x иногда аккумулятором? Нет

  • x - начальное значение, переданное в reduce.

Что такое аккумулятор?

Аккумулятор - это локальная привязка, которая возвращается как значение рекурсивной функции.

Например, если бы мы сами реализовали reduce, мы могли бы сделать это следующим образом:

(defn reduce [f acc coll]
  (if (empty? coll)
    acc
    (recur f (f acc (first coll)) (rest coll))))
  • acc является аккумулятором: являясь аргументом, он локальный.
  • Он возвращается как значение функции, когда coll пуст.

Является ли что-то аккумулятором, зависит от тела функции.

Например, если мы реализуем reverse следующим образом:

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

... то out является аккумулятором.

Но если мы реализуем его следующим образом:

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

... нет аккумулятора.

Внутри вызова reduce acc первоначально привязан к (). Но нет смысла говорить, что () является аккумулятором.