如何编写一个将任意数量的参数传递给另一个函数的Julia函数?
我是 Julia 的新手,对 R 有更多的经验,所以对于那些熟悉两者的人,这里是我试图在 Julia 中复制的一段 R 代码。
f = function(a, b = 1, c = 2) a + 2*b = 3*c
g = function(d, ...) 5 + f(d, ...)
有了这个,调用 将是有效的g(1),它将使用 f 的默认值进行评估,或者您可以指定g(1, b = 3)org(1, c = 4, b = 2)或其他任何内容。重点当然是,只要指定了名称,您就可以将可选参数的任何排列传递给 f,而顺序无关紧要。
在 Julia 中,我在这样做时遇到了一些麻烦,因为 varargs 的工作方式有点不同。我知道您可以将无限的参数列表以元组(例如,function g(d::Number, f_args::NamedTuple)的形式传递给函数,但它似乎不适用于可选的关键字参数。就好像在这条线的某个地方,f_args元组失去了它的名字,并且f因为它看到 3 个没有名字的参数并且不知道选择哪个方法而感到困惑?
如果确实可能的话,如果有人能告诉我如何在 Julia 中做到这一点,我将不胜感激。我想保留这些参数的可选性,以及它们的名称。我还希望能够保留它们的类型(即,我希望 的每个参数f只有在它们适合正确的类型时才可以接受,这当然通常在 Julia 中使用::标头中的运算符完成)。
PS - 就我的问题而言,重写g它c并b作为可选参数对我来说是不够的。为了简单起见,我展示了f和的示例,g但实际上我希望将这些原则应用于具有许多参数的更复杂的函数。
回答
在 julia 中,可选的位置参数和关键字参数被区别对待。
如果我们想在函数中允许任意参数顺序,我们必须指定关键字参数。与 R 不同,julia 坚持我们在参数列表中使用分号来区分位置参数和关键字参数。我们可以编写您的函数f并g在 julia 中通过简单地添加适当的分号来实现这一点:
f(a; b=1, c=2) = a + 2*b + 3*c
g(d; kwargs...) = 5 + f(d; kwargs...)
这定义了一个f具有一个必需位置参数和两个可选关键字参数的方法,以及另一个g具有一个必需位置参数和任意数量的将传递给的关键字参数的方法f。这意味着我们可以这样做:
g(1) # 14
g(1) == 5 + f(1) # true
g(1, b=3) # 18
g(1, c=4, b=2) # 22
同样,与 R 不同,关键字参数与位置参数不同,因此我们不能这样做:
f(1, 2, 4)
# ERROR: MethodError: no method matching f(::Int64, ::Int64, ::Int64)
# Closest candidates are:
# f(::Any; b, c) at REPL[1]:1
这是因为我们定义的方法签名只需要一个位置参数。如果你想要一个可以接受位置参数或关键字参数的函数,我们必须定义一个新的函数签名,如下所示:
f(a, x=1, y=2) = f(a; b=x, c=y)
在这里,我们创建了一些与f原始方法同名的新方法,它们所做的只是将它们的位置参数作为关键字参数传递。现在我们可以这样做:
f(1, 2, 4) == 17 # true
如果我们想g以类似的方式运行,记住 julia 区分位置参数和关键字参数,我们需要指定两个可变参数:一个用于位置参数,一个用于关键字参数:
g(d, args...; kwargs...) = 5 + f(d, args...; kwargs...)
现在我们可以做这样的事情:
g(1, 2) == g(1, b=2) # true
g(1, 2, 4) == g(1, c=4, b=2) # true
但是当我们尝试组合这些类型的签名时,事情会变得混乱:
g(1, 2, c=4)
# ERROR: MethodError: no method matching f(::Int64, ::Int64; c=4)
# Closest candidates are:
# f(::Any, ::Any) at REPL[18]:1 got unsupported keyword argument "c"
# f(::Any, ::Any, ::Any) at REPL[18]:1 got unsupported keyword argument "c"
# f(::Any; b, c) at REPL[18]:1
为什么这是不允许的?错误消息为我们提供了一个关于定义可选位置参数在幕后做了什么的提示。当我们定义一个带有n 个可选位置参数的方法时,julia 只会创建n+1 个方法,每个方法都有 0 到n 个参数被标记到签名的末尾。像这样:
h(a, b=1, c=2, d=3) = nothing
# h (generic function with 4 methods)
methods(h)
# 4 methods for generic function "h":
#[1] h(a) in Main at REPL[32]:1
#[2] h(a, b) in Main at REPL[32]:1
#[3] h(a, b, c) in Main at REPL[32]:1
#[4] h(a, b, c, d) in Main at REPL[32]:1
这意味着,当我们定义的可选位置参数的版本f,朱莉娅取得的签名f(a, b)和f(a, b, c)对我们,但它并没有使带有签名的方法f(a, b; c)。