在Julia中,返回一个元组值以模拟返回多个值。但是,可以在不需要括号的情况下创建和分解元组,从而产生一种幻想,即返回多个值而不是单个元组值。例如,以下函数返回一对值:
julia> function foo(a,b)
a+b, a*b
end
foo (generic function with 1 method)
如果在交互式会话中调用它而未在任何地方分配返回值,则将看到返回的元组:
julia> foo(2,3)
(5, 6)
但是,这种返回值对的典型用法是将每个值提取到变量中。Julia支持简单的元组“解构”,从而简化了此过程:
julia> x, y = foo(2,3)
(5, 6)
julia> x
5
julia> y
6
您还可以通过显式使用return
关键字来返回多个值:
function foo(a,b)
return a+b, a*b
end
与的先前定义完全相同foo
。
能够编写带有任意数量参数的函数通常很方便。此类函数在传统上称为“可变参数”函数,是“可变数量的参数”的缩写。您可以在最后一个参数后面加上省略号来定义varargs函数:
julia> bar(a,b,x...) = (a,b,x)
bar (generic function with 1 method)
变量a
和b
通常绑定到前两个参数值,变量x
绑定到bar
在其前两个参数之后传递的零个或多个值的可迭代集合:
julia> bar(1,2)
(1, 2, ())
julia> bar(1,2,3)
(1, 2, (3,))
julia> bar(1, 2, 3, 4)
(1, 2, (3, 4))
julia> bar(1,2,3,4,5,6)
(1, 2, (3, 4, 5, 6))
在所有这些情况下,x
都绑定到传递给的尾随值的元组bar
。
可以限制作为变量参数传递的值的数量。稍后将在参数约束Varargs方法中对此进行讨论。
另一方面,将可迭代集合中包含的值作为单独的参数“拼接”到函数调用中通常很方便。为此,还可以...
在函数调用中使用but:
julia> x = (3, 4)
(3, 4)
julia> bar(1,2,x...)
(1, 2, (3, 4))
在这种情况下,值的元组被精确地连接到varargs调用中,该变量位于可变参数数目所在的位置。但是,不必如此:
julia> x = (2, 3, 4)
(2, 3, 4)
julia> bar(1,x...)
(1, 2, (3, 4))
julia> x = (1, 2, 3, 4)
(1, 2, 3, 4)
julia> bar(x...)
(1, 2, (3, 4))
此外,拼接到函数调用中的可迭代对象不必是元组:
julia> x = [3,4]
2-element Array{Int64,1}:
3
4
julia> bar(1,2,x...)
(1, 2, (3, 4))
julia> x = [1,2,3,4]
4-element Array{Int64,1}:
1
2
3
4
julia> bar(x...)
(1, 2, (3, 4))
同样,参数要加入的函数不必是varargs函数(尽管通常是这样):
julia> baz(a,b) = a + b;
julia> args = [1,2]
2-element Array{Int64,1}:
1
2
julia> baz(args...)
3
julia> args = [1,2,3]
3-element Array{Int64,1}:
1
2
3
julia> baz(args...)
ERROR: MethodError: no method matching baz(::Int64, ::Int64, ::Int64)
Closest candidates are:
baz(::Any, ::Any) at none:1
如您所见,如果拼接容器中的元素数量错误,则函数调用将失败,就像显式给出太多参数一样。
在许多情况下,函数参数具有合理的默认值,因此可能不需要在每次调用中显式传递。例如,库函数parse(T, num, base)
将字符串解释为某个基数的数字。该base
参数默认为10
。此行为可以简明表示为:
function parse(type, num, base=10)
###
end
使用此定义,可以使用两个或三个参数调用该函数,并且10
在未指定第三个参数时会自动传递该函数:
julia> parse(Int,"12",10)
12
julia> parse(Int,"12",3)
5
julia> parse(Int,"12")
12
可选参数实际上只是用于编写具有不同数量参数的多个方法定义的便捷语法(请参阅有关可选参数和关键字Arguments的注释)。
一些函数需要大量的参数,或具有大量的行为。记住如何调用此类函数可能很困难。关键字参数可以通过名称而不是位置来标识,从而使这些复杂的界面更易于使用和扩展。
例如,考虑plot
绘制线的函数。此功能可能有许多选项,用于控制线条样式,宽度,颜色等。如果它接受关键字参数,可能的调用可能类似于plot(x, y, width=2)
,其中我们选择仅指定线宽。请注意,这有两个目的。该调用更易于阅读,因为我们可以用其含义标记一个自变量。也可以按任何顺序传递大量参数的任何子集。
具有关键字参数的函数在签名中使用分号定义:
function plot(x, y; style="solid", width=1, color="black")
###
end
调用函数时,分号是可选的:可以调用plot(x, y, width=2)
或plot(x, y; width=2)
,但是前一种样式更常见。显式分号仅在如下所述传递变量或参数时才需要。
仅在必要时(未传递相应的关键字参数时)并按从左到右的顺序评估关键字参数的默认值。因此,默认表达式可以引用先前的关键字参数。
关键字参数的类型可以如下明确:
function f(;x::Int64=1)
###
end
可以使用来收集额外的关键字参数...
,如varargs函数中所示:
function f(x; y=0, kwargs...)
###
end
在中f
,kwargs
将是一个(key,value)
元组集合,其中每个元组key
都是一个符号。可以在调用中使用分号将此类集合作为关键字参数传递f(x, z=1; kwargs...)
。字典也可以用于此目的。
一个人也可以传递(key,value)
元组,或=>
可以在分号后显式指定的任何可分配给该元组的可迭代表达式(例如,对)。例如,plot(x, y; (:width,2))
和plot(x, y; :width => 2)
等价于plot(x, y, width=2)
。在运行时计算关键字名称的情况下,这很有用。
关键字参数的性质使得可以多次指定同一参数。例如,在调用plot(x, y; options..., width=2)
中,options
结构也可能包含的值width
。在这种情况下,最右边的事件优先。在此示例中,width
肯定具有值2
。
可选参数和关键字参数在评估其默认值方面略有不同。评估可选参数默认表达式时,只有先前的参数在范围内。相反,当评估关键字参数默认表达式时,所有参数都在范围内。例如,给定以下定义:
function f(x, a=b, b=1)
###
end
将b
在a=b
指的是b
在一个外部范围,而不是随后的参数b
。但是,如果a
和b
是关键字参数,则两者都将在同一范围内创建,而b
in a=b
将引用后续参数b
(b
在外部范围内阴影),这将导致未定义的变量错误(因为默认表达式为从左到右评估,并且b
尚未分配)。
将函数作为参数传递给其他函数是一种强大的技术,但是其语法并不总是很方便。当function参数需要多行时,编写此类调用特别麻烦。例如,考虑map()
在几种情况下调用函数:
map(x->begin
if x < 0 && iseven(x)
return 0
elseif x == 0
return 1
else
return x
end
end,
[A, B, C])
Julia提供了一个保留字do
来更清楚地重写此代码:
map([A, B, C]) do x
if x < 0 && iseven(x)
return 0
elseif x == 0
return 1
else
return x
end
end
该do x
语法使用参数创建一个匿名函数,x
并将其作为第一个参数传递给map()
。类似地,do a,b
将创建一个包含两个参数的匿名函数,而平原do
将声明其后是形式为的匿名函数() -> ...
。
这些参数的初始化方式取决于“外部”功能。在这里,map()
将依次设定x
到A
,B
,C
,呼吁每个匿名函数,就如同将在语法发生map(func, [A, B, C])
。
由于调用看起来像普通的代码块,因此此语法使使用功能更有效地扩展语言变得更加容易。与可能有许多用途大不相同map()
,例如管理系统状态。例如,有一个版本open()
可以运行代码,以确保最终关闭打开的文件:
open("outfile", "w") do io
write(io, data)
end
这是通过以下定义完成的:
function open(f::Function, args...)
io = open(args...)
try
f(io)
finally
close(io)
end
end
在这里,open()
首先打开要写入的文件,然后将结果输出流传递给您在do ... end
块中定义的匿名函数。函数退出后open()
,无论函数正常退出还是引发异常,都将确保流已正确关闭。(该try/finally
构造将在“ 控制流”中进行描述。)
使用do
块语法,可以帮助检查文档或实现,以了解如何初始化用户函数的参数。
在技术计算语言中,通常会使用功能的“向量化”版本,该版本仅将给定功能f(x)
应用于数组的每个元素A
以通过产生新的数组f(A)
。这种语法对于数据处理很方便,但是在其他语言中,性能通常也需要向量化:如果循环很慢,则函数的“向量化”版本可以调用用低级语言编写的快速库代码。在Julia中,矢量化函数并不是提高性能所必需的,确实,编写自己的循环通常是有好处的(请参见Performance Tips),但是它们仍然很方便。因此,任何 Julia函数f
可以使用语法逐元素地应用于任何数组(或其他集合)f.(A)
。例如,sin
可以将其应用于vector中的所有元素A
,如下所示:
julia> A = [1.0, 2.0, 3.0]
3-element Array{Float64,1}:
1.0
2.0
3.0
julia> sin.(A)
3-element Array{Float64,1}:
0.841471
0.909297
0.14112
当然,如果您编写了专门的“向量”方法(f
例如通过)f(A::AbstractArray) = map(f, A)
,则可以省略点,这与一样有效f.(A)
。但是,这种方法要求您预先确定要向量化的功能。
更一般地说,f.(args...)
实际上等效于broadcast(f, args...)
,它允许您对多个数组(甚至具有不同形状)进行操作,或者对数组和标量进行混合操作(请参见Broadcasting)。例如,如果你有f(x,y) = 3x + 4y
,那么f.(pi,A)
将返回由一个新的数组f(pi,a)
的每个a
中A
,并且f.(vector1,vector2)
将返回由一个新的向量f(vector1[i],vector2[i])
为每个索引i
(抛出异常,如果载体具有不同的长度)。
julia> f(x,y) = 3x + 4y;
julia> A = [1.0, 2.0, 3.0];
julia> B = [4.0, 5.0, 6.0];
julia> f.(pi, A)
3-element Array{Float64,1}:
13.4248
17.4248
21.4248
julia> f.(A, B)
3-element Array{Float64,1}:
19.0
26.0
33.0
而且,嵌套 f.(args...)
调用被合并到一个broadcast
循环中。例如,sin.(cos.(X))
等于broadcast(x -> sin(cos(x)), X)
,类似于[sin(cos(x)) for x in X]
:类似:仅存在一个循环X
,并且为结果分配了一个数组。[相反,sin(cos(X))
在典型的“向量化”语言中,首先会为分配一个临时数组tmp=cos(X)
,然后sin(tmp)
在单独的循环中进行计算,再分配第二个数组。]这种循环融合不是编译器的优化,它可能会发生也可能不会发生,而是遇到嵌套调用时的语法保证f.(args...)
。从技术上讲,一旦遇到“非点”函数调用,融合就会停止;例如,在sin.(sort(cos.(X)))
所述sin
并cos
由于存在中间sort
功能,因此无法合并循环。
最后,当向量化操作的输出数组被预先分配时,通常可以实现最大效率,因此重复调用不会为结果一遍又一遍地分配新数组(预分配输出:)。方便的语法是X .= ...
,它等效于,broadcast!(identity, X, ...)
除了如上所述,broadcast!
循环与任何嵌套的“点”调用融合在一起。例如,X .= sin.(Y)
等效于broadcast!(sin, X, Y)
,X
用sin.(Y)
就地覆盖。如果左手侧是一个数组索引表达,例如X[2:end] .= sin.(Y)
,然后将其转换为broadcast!
一个view
,例如broadcast!(sin, view(X, 2:endof(X)), Y)
,使得左手侧被就地更新。
由于在表达式中的许多操作和函数调用中添加点可能很麻烦,并且导致难以阅读的代码,@.
因此提供了宏,可将表达式中的每个函数调用,操作和赋值转换为“点分”版本。
julia> Y = [1.0, 2.0, 3.0, 4.0];
julia> X = similar(Y); # pre-allocate output array
julia> @. X = sin(cos(Y)) # equivalent to X .= sin.(cos.(Y))
4-element Array{Float64,1}:
0.514395
-0.404239
-0.836022
-0.608083
类似的二进制(或一元)运算符.+
使用相同的机制处理:它们等效于broadcast
调用,并与其他嵌套的“点”调用融合。 X .+= Y
etcetera等同于X .= X .+ Y
并导致融合的就地分配