Функции более высокого порядка в Clojure
Clojure является удивительным, мы все это знаем, но это не главное. Мне интересно, какой идиоматический способ создания и управления функциями более высокого порядка по-Haskell-подобному. В Clojure я могу сделать следующее:
(defn sum [a b] (+ a b))
Но (sum 1)
не возвращает функцию: она вызывает ошибку. Конечно, вы можете сделать что-то вроде этого:
(defn sum
([a] (partial + a))
([a b] (+ a b)))
В этом случае:
user=> (sum 1)
#<core$partial$fn__3678 [email protected]>
user=> ((sum 1) 2)
3
Но это не похоже на правильный путь. Любые идеи?
Я не говорю о реализации функции sum
, я говорю на более высоком уровне абстракции. Существуют ли какие-либо идиоматические шаблоны? Некоторые макросы? Является ли лучший способ определения макроса или существуют альтернативные решения?
Ответы
Ответ 1
Кто-то уже выполнил это в группе Clojure. Вы можете указать, сколько аргументов имеет функция, и оно будет зависеть от вас до тех пор, пока оно не получит столько.
Причина, по которой это не происходит по умолчанию в Clojure, заключается в том, что мы предпочитаем вариационные функции для автоматических валют. Я полагаю.
Ответ 2
Я немного поиграл с функциями, предложенными amalloy. Мне не нравится явная спецификация числа аргументов для curry on. Поэтому я создал свой собственный макрос. Это старый способ специфической функции высокого порядка:
(defn-decorated old-sum
[(curry* 3)]
[a b c]
(+ a b c))
Это мой новый макрос:
(defmacro defn-ho
[fn-name & defn-stuff]
(let [number-of-args (count (first defn-stuff))]
`(defn-decorated ~fn-name [(curry* ~number-of-args)] [email protected])))
И это новый неявный способ:
(defn-ho new-sum [a b c] (+ a b c))
Как вы можете видеть, нет следов (карри) и других вещей, просто определите свою текущую функцию как раньше.
Ребята, что вы думаете? Идеи? Предложения?
Bye!
Alfedo
Изменить: я изменил макрос в соответствии с проблемой amalloy о docstring. Это обновленная версия:
(defmacro defhigh
"Like the original defn-decorated, but the number of argument to curry on
is implicit."
[fn-name & defn-stuff]
(let [[fst snd] (take 2 defn-stuff)
num-of-args (if (string? fst) (count snd) (count fst))]
`(defn-decorated ~fn-name [(curry* ~num-of-args)] [email protected])))
Мне не нравится оператор if во второй привязке. Любые идеи о том, чтобы сделать его более сукцином?
Ответ 3
Это позволит вам делать то, что вы хотите:
(defn curry
([f len] (curry f len []))
([f len applied]
(fn [& more]
(let [args (concat applied (if (= 0 (count more)) [nil] more))]
(if (< (count args) len)
(curry f len args)
(apply f args))))))
Здесь, как его использовать:
(def add (curry + 2)) ; read: curry plus to 2 positions
((add 10) 1) ; => 11
Условное значение с [nil]
предназначено для обеспечения того, чтобы каждое приложение обеспечивало некоторый прогресс вперед в состоянии карри. Там есть длинное объяснение, но я нашел это полезным. Если вам не нравится этот бит, вы можете установить args как:
[args (concat applied more)]
В отличие от JavaScript мы не имеем возможности узнать о arity переданной функции и поэтому вы должны указать ожидаемую длину. Это имеет большое значение в Clojure [Script], где функция может иметь несколько arities.