最一般的是多行形式:
function muladd(x, y, z)
x * y + z
end
简短定义则用“赋值”形式,也叫“单行”形式:
muladd(x, y, z) = x * y + z
还有第三种更简短的形式:可以创建一个简短、只用一次的函数,而且不用给它起名字:
julia> map(x -> 2x, 1:3)
4-element Vector{Int64}:
2
4
6
julia> map((x, y) -> x * y, 1:3, 4:6)
3-element Vector{Int64}:
4
10
18
在这里,x -> 2x 就是一个“匿名函数”。它相当于其他一些语言中所说的“lambda 函数”。
注意,多个实参需要用括号括起来,比如 (x, y) -> x * y。
匿名函数在 Julia 代码中很常见,尤其是和 map()、filter() 这类高阶函数一起使用时。
到目前为止,教学大纲里我们看过的函数都只接受固定数量的实参,而且调用时必须按正确的顺序把它们全部传进去。这样限制太多,用起来也不方便,所以 Julia 还提供了其他几种选择。
和很多语言一样,Julia 允许在定义函数时为单个实参指定默认值。
调用函数时,既可以给这个实参传值,也可以省略它,直接使用默认值。
julia> f(x, y=10) = x * y
f (generic function with 2 methods)
julia> f(2, 3)
6
julia> f(2)
20
所有_没有_默认值的实参都必须排在_有_默认值的实参前面,也就是说 f(x=2, y) 是无效的。
到目前为止,所有例子用的都是 positional arguments,即调用函数时传入的值必须和函数定义中对应实参的顺序一致。
和很多语言一样,Julia 也支持 keyword arguments。调用时必须写出实参的名字,但多个关键字实参可以按任意顺序给出。
Julia 的一个特色是:函数定义里的关键字实参(如果有的话)前面必须加一个分号 ;,把它和位置实参分开。调用时,最后一个位置实参和第一个关键字实参之间用 ; 或 , 都可以。
julia> b(x; y) = x + y
b (generic function with 1 method)
julia> b(2, y=3)
5
# keyword is required when calling
julia> b(2, 3)
ERROR: MethodError: no method matching b(::Int64, ::Int64)
The function `b` exists, but no method is defined for this combination of argument types.
和位置实参一样,关键字实参也可以选择性地指定默认值。
如果预先算好了一个变量,而它的名字和形参相同,那么调用时经常会出现 myarg=myarg 这样的写法。这种情况下可以使用简写形式:
julia> width = 4.0
4.0
julia> height = √ width
2.0
julia> area(; width, height) = width * height
area (generic function with 1 method)
# repetition
julia> area(; width=width, height=height)
8.0
# shorthand form
julia> area(; width, height)
8.0
说不定你会好奇,这两个词是 Julia 语法中一个很实用的方面的标准叫法。两者都指 ... 运算符。
Splat 用在函数_调用_中,把集合展开成函数所需要的各个值。
这个用例子演示比用语言解释更容易:
julia> fxyz(x, y, z) = x * y * z
fxyz (generic function with 1 method)
julia> xyz = [2, 3, 4]
3-element Vector{Int64}:
2
3
4
# Using the vector directly in a function call is invalid
julia> fxyz(xyz)
ERROR: MethodError: no method matching fxyz(::Vector{Int64})
The function `fxyz` exists, but no method is defined for this combination of argument types.
# splatting converts the vector to 3 numbers, used as positional arguments
julia> fxyz(xyz...)
24
有些“函数调用”被语法糖隐藏了起来,所以 Splat 也能用在不太明显的地方。
例如,多重赋值在内部就用到了元组构造函数:
julia> first, rest... = [1, 2, 3, 4]
4-element Vector{Int64}:
1
2
3
4
julia> first
1
julia> rest
3-element Vector{Int64}:
2
3
4
关键字实参也可以用 Splat 传入,通常借助 named tuple。
Dict 也可以,但键必须是符号(这里字符串不行)。
# function with 3 keyword arguments
julia> fabc(; a, b, c) = a + b + c
fabc (generic function with 1 method)
# named tuple
julia> abc_nt = (a=2, b=3, c=4)
(a = 2, b = 3, c = 4)
# there are no positional arguments, so need to use ; before kw argument
julia> fabc(;abc_nt...)
9
# Dict
julia> abc_dict = Dict(:a=>2, :b=>3, :c=>4)
Dict{Symbol, Int64} with 3 entries:
:a => 2
:b => 3
:c => 4
julia> fabc(;abc_dict...)
9
Slurp 用在函数_定义_中,把任意数量的值打包进一个集合。
julia> f_more(i, j, more...) = i + j + sum(more)
f_more (generic function with 1 method)
julia> f_more(1, 3, 5, 7, 9, 11)
36
收集进来的那个实参叫什么名字(这里是 more)并不重要。这个变量的类型由编译器决定,但对于位置实参来说,通常是 tuple 或类似的东西。
关键字实参也可以收集起来,得到的是一个 Dict(或类似的东西)。
julia> f_kwslurp(x, y; switches...) = :mult in keys(switches) ? x * y : x + y
f_kwslurp (generic function with 1 method)
julia> f_kwslurp(5, 6; mult=true)
30
julia> f_kwslurp(5, 6)
11
调用时可以使用任意关键字实参。至于响应哪些关键字、忽略哪些,由函数定义自己决定。