Proc#curry和splat运算符:使用任意数量的参数进行Currying

时间:2013-02-07 13:50:59

标签: ruby functional-programming currying

Ruby 1.9's built in support of currying支持两种方法来处理一个带有任意数量参数的proc:

my_proc = proc {|*x| x.max }

1)curry没有参数:my_proc.curry。你将逗号分隔的参数传递给curried的proc,就像你对普通的proc一样。如果参数的数量是任意的(如果某些参数不是splat那么有用),这就无法实现正确的currying。

2)curry带有参数:my_proc.curry(n)这样,currying就会被应用,好像proc会接受n个参数。例如:

my_proc.curry(3).call(2).call(5).call(1) #=> 5

那么,你将如何通过任意数量的参数实现讨论呢?这意味着,如果没有给出n

我想到的一种方法是通过代理call收集参数,然后通过proc解析method_missing(如果使用call以外的任何方法/ call没有参数,使用收集的参数调用proc,但我仍然在寻找其他方法来实现它。

更新

正如Andy H所说,问题是什么时候停止晃动。对于我的目的,如果调用停止/ proc评估何时调用除call之外的任何方法或者在没有参数的情况下调用call,那就没关系。

3 个答案:

答案 0 :(得分:7)

内置curry方法不适合您。原因是它产生一个proc,只要它有足够的参数就可以进行评估。引用您链接到的文档:

  

一个咖喱过程得到一些论点。如果提供了足够数量的参数,它会将提供的参数传递给原始proc并返回结果。

这里要实现的关键点是零是splat参数的“足够数量的参数”。

f = ->(x, y, z, *args){ [x, y, z, args] } 
g = f.curry    # => a "curryable" proc
h = g.call(1)  # => another curryable proc
i = h.call(2)  # => another curryable proc
j = i.call(3)  # => [1, 2, 3, []]

如你所示,这些“可以理解”的过程可以一次一个地传递它们的参数,每次都返回一个新的curryable过程,直到有足够的参数传递,此时它会进行评估。这也是为什么他们不能支持任意长度的参数列表 - 它怎么知道何时停止currying并且只是评估?

如果你想要一种允许任意数量的参数的不同的currying方式,你可以定义自己的咖喱方法:

def my_curry(f, *curried_args)
  ->(*args) { f.call(*curried_args, *args) }
end

这是一个相当简单的实现,但可能符合您的目的。与内置方法的主要区别在于总是返回一个新的proc,即使已经传递了足够的参数,并且它不支持一次一个“咖喱链” ”

f = ->(x, y, z, *args) { [x, y, z, args] }

g = my_curry(f, 1)    # => a proc
g.call(2, 3)          # => [1, 2, 3, []] 
g.call(2, 3, 4, 5)    # => [1, 2, 3, [4, 5]]
g.call(2)             # => ArgumentError: wrong number of arguments (2 for 3)

h = my_curry(g, 2, 3) # => a proc
h.call                # => [1, 2, 3, []]
h.call(4, 5)          # => [1, 2, 3, [4, 5]]

答案 1 :(得分:1)

我讨厌变得愚蠢,但忘了curry并写下来

my_proc = proc { |*x| x.max }
l = -> *x { my_proc.( 4, 6, 12, 1, *x ) }
l.call + 1 #=> 13

你知道,Matz自己说curry“只是一个功能性孩子的玩具”。它的实现并不特殊,它与您上面看到的并不快或不同。 Curry在Ruby中没那么有用......

对于那些不相信我说的人,这里是确切的引用和链接:

Easter Egg for Functional Programming Kids

答案 2 :(得分:0)

我实现了一个使用Basic Object的子类作为包装器来解决问题的解决方案:

class CurryWrap < BasicObject

  def initialize proc
    @proc = proc
    @args = []
    return self
  end

  def call *args
    if args.size == 1
      @args << args.first
      return self
    elsif args.empty?
      return @proc.call(*@args)
    else
      @proc.call(*@args).call(*args)
    end
  end

  def == other
    @proc.call(*@args) == other
  end

  def method_missing m, *args, &block
    @proc.call(*@args).send(m, *args, &block)
  end

end

有了这个,基本上可以做到以下几点:

my_proc = proc { |*x| x.max }

CurryWrap.new(my_proc).call(4).call(6).call(12).call(1) + 1 #=> 13

除了带有参数的call之外的任何内容都将解析proc。

我不知道这是否是最优雅的方式,所以我暂时不接受答案。