карри в CoffeeScript - PullRequest
       4

карри в CoffeeScript

16 голосов
/ 04 декабря 2011

Я играл с 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)

И то же самое для uncurry:

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_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 с применением функции для полученияВыделка.Вот что я пытался сделать:

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)

приводит к следующей ошибке:

Function.prototype.apply: список аргументов имеет неправильный тип

сейчаспосле нескольких заметок я понимаю, что попытка составить f с помощью curry_list некорректна.У меня есть интуиция, что я ищу что-то похожее на эту композицию, но это не совсем так.Правильно ли я считаю, что?

Наконец, что будет правильной реализацией?

Ответы [ 5 ]

7 голосов
/ 04 декабря 2011

Вы возвращаете составленную функцию после 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
3 голосов
/ 17 августа 2012

Я не думаю, что это становится немного проще, чем это:

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
2 голосов
/ 04 декабря 2011

Соответствующее обобщение представлено в Частично со свободными переменными .

2 голосов
/ 04 декабря 2011

Это не похоже на композицию.Последняя реализация curry, которая у вас есть, похоже, не делает различий между функцией, которую нужно каррировать, и аргументами этой функции, тогда как может показаться, что такое различие довольно важно.Как насчет этого?

curry = (n, f) ->
    acc = []
    helper = (x) ->
        acc.push x
        if acc.length is n then f acc... else helper
0 голосов
/ 07 марта 2012

Я действительно заинтересовался решением этой проблемы, поэтому я написал это. Вероятно, требуется несколько настроек, но, насколько я тестировал, он работает потрясающе. По сути, просто вызовите f.curry (), который возвращает частично примененные функции по очереди ... пока вы не вызовете его с последним аргументом, который он принимает, то есть когда этот частичный вызов вызывает тот, что перед ним, и так далее, вплоть до оригинал ф.

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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...