Статьи

Clojure: первые шаги с редукторами

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

1
2
3
4
5
6
7
(defn log2 [n]
  (/ (Math/log n) (Math/log 2)))
  
(defn score-item [n]
  (if (= n 0) 0 (log2 n)))
  
(+ (score-item 12) (score-item 13) (score-item 5)) 9.60733031374961

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

1
(reduce #(+ %1 (score-item %2)) 0 [12 13 5]) 9.60733031374961

Дополнительное преимущество заключается в том, что если я хочу добавить 4-й счет к миксу, все, что мне нужно сделать, это добавить его в конец вектора:

1
(reduce #(+ %1 (score-item %2)) 0 [12 13 5 6]) 12.192292814470767

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

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

Нашим первым шагом является введение пространства имен в область видимости:

1
(require ' [ clojure.core.reducers :as r])

Теперь мы можем вычислить тот же результат, используя функцию Reduce:

1
(r/reduce #(+ %1 (score-item %2)) 0 [12 13 5 6]) 12.192292814470767

Пока что так идентично. Если бы мы хотели рассчитать отдельные оценки, а затем отфильтровать их ниже определенного порога, код работал бы немного иначе:

1
2
3
4
5
6
7
(->>[12 13 5 6]
    (map score-item)
    (filter #(> % 3))) (3.5849625007211565 3.700439718141092)
  
(->> [12 13 5 6]
     (r/map score-item)
     (r/filter #(> % 3))) #object [ clojure.core.reducers$folder$reify__19192 0x5d0edf21 " clojure.core.reducers$folder$reify__19192@5d0edf21 "]

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

1
2
3
4
(->> [12 13 5 6]
     (r/map score-item)
     (r/filter #(> % 3))
     (into [])) (3.5849625007211565 3.700439718141092)

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

Один на следующий раз!

Ссылка: Clojure: Первые шаги с редукторами от нашего партнера JCG Марка Нидхэма в блоге Марка Нидхэма .