CoffeeScript中的n-ary咖喱

时间:2011-12-04 04:54:39

标签: javascript functional-programming 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_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)

产生以下错误:

  

Function.prototype.apply:参数列表的类型错误

现在在记下一些笔记之后,我明白尝试用curry_list编写f是不正确的。我确实有直觉,我正在寻找的东西看起来像这个组合,但并非如此。我认为这是正确的吗?

最后,什么是正确的实施?

5 个答案:

答案 0 :(得分:7)

您将在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

答案 1 :(得分:3)

我认为它不比这更简单:

Function::curry = (n) ->
  if n is 1 then @ else (x) => (=> @ x, arguments...).curry(n-1)

基本上,如果请求的arity大于1,那么它将使用一个参数并返回另一个函数并在此过程中构造一个函数梯。它一直这样,直到arity为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 :(得分:2)

它看起来不像是作曲。你拥有的最后一个curry实现似乎没有区分curried函数和该函数的参数,而看起来这种区别是相当重要的。这样的事情怎么样?

curry = (n, f) ->
    acc = []
    helper = (x) ->
        acc.push x
        if acc.length is n then f acc... else helper

答案 3 :(得分:2)

Partial with Free Variables中提供了相关的概括。

答案 4 :(得分:0)

我对解决这个问题非常感兴趣,所以我写了这篇文章。它可能需要一些调整,但就我测试而言,它的工作效果非常好。基本上只是调用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)