如何在 Ruby 中预填充函数的参数?

How to pre-fill arguments to a function in Ruby?

假设我有一个函数 foo:

def foo(A, B, C)
 A + B + C
end

我这样称呼它,只更改最后一个参数:

foo("foo", "bar", "123")
foo("foo", "bar", "456")
foo("foo", "bar", "789")

如何“烘焙”或“预填充”不更改的参数?所以也许我会得到一个新的可调用 foo_baked 使得 foo_baked("123")foo("foo", "bar", "123")?

相同

并像这样使用它:

foo_baked = ...?
foo_baked("123")
foo_baked("456")
foo_baked("789")

请注意,我不想使用 def 定义新函数,但希望能够在运行时动态创建 foo_baked,也许是一个数组。

您可以定义一个新方法 foo_baked 来包装 foo 并将 non-changeable 参数作为硬编码值传递:

def foo_baked(c)
  foo("foo", "bar", c)
end

然后可以这样调用:

foo_baked("123")

或者,您可以使用 Ruby 的 built-in #curry 方法,如 中所述。这种方法更灵活,返回一个可调用的过程,直到提供足够的参数。

你有几个选择。第一种是 Zoran 的回答中概述的 currying 方法。

第二种是使用默认位置参数。但为了做到这一点,可选参数需要 所需参数之后:

def foo_baked(a, b="foo", c="bar")
  [a, b, c]
end

foo_baked(123) # => [123, "foo", "bar"]
foo_baked(123, "asd") 

第三个选项是使用关键字参数。这种方法的好处是参数可以按任何顺序排列。您可以挑选并选择您为其提供值的那些。

def foo_baked(b: "foo", c: "bar", a:)
  [a, b, c]
end

foo_baked(a: 123) # => [123, "foo", "bar"]
foo_baked(a: 123, b: "asd") # => [123, "asd", "bar"]

最后一个选择是结合位置参数和关键字参数:

def foo_baked(a, b: "foo", c: "bar")
  [a, b, c]
end

foo_baked(123) # => [123, "foo", "bar"]
foo_baked(123, b: "asd") # => [123, "asd", "bar"]

这样做的好处是您仍然可以使用位置参数,但可选参数的顺序无关紧要,它们永远不会干扰位置参数

您可以创建一个 lambda 包装原始方法:

foo_baked = ->(value) { foo('foo', 'bar', value) }

foo_baked.(123) # => [123, "foo", "bar"]

请注意执行过程中的附加 .。这是ruby特殊语法,相当于:

foo_baked.call(123)

Ruby 有 function-currying built in with the curry 方法:

def foo(a, b, c)
  a + b + c
end

foo_baked = method(:foo).curry.call('foo', 'bar')

# this returns "foobar123"
foo_baked.call('123')

# ArgumentError (wrong number of arguments (given 4, expected 3))
foo_baked.call('123', '234')

基本上,Method#curry returns curried proc:一个函数,pre-loaded 带有参数,只有在传递了足够的参数以满足方法签名后才会执行。

注意 foo_baked.lambda? returns true,所以柯里化过程是 actually a lambda。这很重要,因为这意味着如果您超过参数的最大数量,您将获得 ArgumentError

您可以通过将 arity 参数传递给 curry.

来允许超出所需三个参数的其他参数
def foo(*args)
  args.join
end

# does not execute since only 2 arguments have been supplied
foo_baked = method(:foo).curry(3).call('foo', 'bar')

# executes on the third argument
# this returns "foobar123"
foo_baked.call('123')

# this returns "foobar123234"
foo_baked.call('123', '234')