N-ary curry в CoffeeScript
Я играл с CoffeeScript, когда обнаружил, что пишу следующие строки, а затем смотрю на них с благоговением:
compose = (f, g) -> (x) -> f g x
curry = (f) -> (x) -> (y) -> f(x, y)
uncurry = (f) -> (x, y) -> (f x) y
Как хорошо, я думал! Теперь, как упражнение, я думал, что обобщил функции curry и uncurry для n args, чтобы получить что-то похожее на это:
curry2 = (f) -> (x) -> (y) -> f(x, y)
curry3 = (f) -> (x) -> (y) -> (z) -> f(x, y, z)
curry4 = (f) -> (x) -> (y) -> (z) -> (t) -> f(x, y, z, t)
И то же самое для неуправляемого:
uncurry2 = (f) -> (x, y) -> (f x) y
uncurry3 = (f) -> (x, y, z) -> ((f x) y) z
uncurry4 = (f) -> (x, y, z, t) -> (((f x) y) z) t
Написание n-ary uncurry было не очень сложно:
uncurry = (n) -> (f) -> (args...) ->
if n == 1
f args[0]
else
((uncurry n - 1) f args.shift()) args...
С другой стороны, я не могу понять, как заставить n-ary curry работать. Я думал о реализации первой функции curry_list, которая является обобщением этого пакета:
curry_list2 = (f) -> (x) -> [x, y]
curry_list3 = (f) -> (x) -> (z) -> [x, y, z]
curry_list4 = (f) -> (x) -> (z) -> (t) -> [x, y, z, t]
Здесь реализация:
curry_list = (n) ->
curry_list_accum = (n, accum) ->
if n
(x) ->
accum.push x
curry_list_accum n - 1, accum
else
accum
curry_list_accum n, []
И тогда я бы просто составил curry_list с приложением функции, чтобы получить currying. Это то, что я пытался сделать:
curry = (n) ->
apply_helper = (f) -> (args) -> f args...
(f) -> compose (apply_helper f), (curry_list n)
Но по какой-то причине это не работает. Например, пытаясь оценить
curry(3)((a,b,c) -> a + b + c)(1)(2)(3)
выдает следующую ошибку:
Функция .prototype.apply: список аргументов имеет неправильный тип
Теперь, после того, как я записал несколько заметок, я понимаю, что попытка скомпоновать f с curry_list неверна. У меня есть интуиция, что то, что я ищу, - это нечто похожее на эту композицию, но это не совсем так. Правильно ли я это думаю?
Наконец, что было бы правильной реализацией?
Ответы
Ответ 1
Вы возвращаете сложенную функцию после curry(3)((a,b,c) -> a + b + c)
, а не аккумулятора.
Это означает, что ((args) -> f args...)
получает функцию как аргумент, ваш код не дожидается завершения списка аргументов, чтобы вызвать f
.
Может быть, реализовать это без композиции?
accumulator = (n, accum, f) ->
return f accum... if n is 0
(x) ->
accum.push x
accumulator n - 1, accum, f
curry = (n) ->
(f) -> accumulator n, [], f
curry(3)((a,b,c) -> a + b + c)(1)(2)(3) # 6
Ответ 2
Я не думаю, что это будет проще, чем это:
Function::curry = (n) ->
if n is 1 then @ else (x) => (=> @ x, arguments...).curry(n-1)
В принципе, если запрашиваемая арность больше 1, она потребляет аргумент и возвращает другую функцию и создает лестницу функций на этом пути. Он делает это до тех пор, пока арность не станет равной 1, и в этом случае она поднимается по лестнице созданных ею функций. Лестница добавляет потребляемые аргументы в список, пока не будет вызвана функция в верхней части лестницы, которая является предоставленной пользователем.
Вот некоторые тесты, которые я написал в случае, если вам интересно:
add3 = ((a,b,c) -> a+b+c).curry 3
add3_1 = add3 1
add3_1_2 = add3_1 2
console.log add3_1(4)(5) is 10
console.log add3_1(4)(6) is 11
console.log add3_1_2(4) is 7
console.log add3_1(5)(5) is 11
Ответ 3
Мне не нравится композиция. Последняя реализация curry
, которую вы, по-видимому, не делает различий между функцией, которая должна быть нарисована, и аргументами этой функции, тогда как представляется, что такое различие является весьма важным. Как насчет чего-то подобного?
curry = (n, f) ->
acc = []
helper = (x) ->
acc.push x
if acc.length is n then f acc... else helper
Ответ 4
Связанное обобщение представлено в Частично со свободными переменными.
Ответ 5
Мне очень понравилось это решение, поэтому я написал это. Это, вероятно, нуждается в нескольких настройках, но это работает очень хорошо, насколько я тестировал. В основном просто вызовите f.curry(), который последовательно возвращает частично применяемые функции.. до тех пор, пока вы не назовете его последним аргументом, который он принимает, а именно, когда тот частичный вызывает тот, который был перед ним и т.д., Полностью назад оригинал f.
partial = (f, args1...) -> (args2...) ->
f.apply @, args1.concat args2
partial$ = (f, args) ->
partial.apply @, [f].concat args
Function::curry = (args...) ->
curry$ = (n, f, args...) ->
curry$$ = (n, f, args...) ->
if n > args.length
partial curry$$, (n - args.length), (partial$ f, args)
else f args
curry$$ (n - args.length), (partial$ f, args)
curry$.apply @, [@length, @].concat args
Теперь мы можем делать такие вещи:
twoToThe = Math.pow.curry 2
32 == twoToThe 5
32 == Math.pow.curry()(2)(5)
32 == Math.pow.curry()(2, 5)
32 == Math.pow.curry(2, 5)
32 == Math.pow.curry(2)(5)
# And just for laughs:
32 == Math.pow.curry()()()(2)()()()(5)