贡献者: xzllxls
本文授权转载自郝林的 《Julia 编程基础》。原文链接:第 12 章 函数与方法。
我们自从在讲 BigFloat
的时候提了一下 do
代码块,之后就再也没有谈论到它了。但实际上,do
代码块是一个很棒的语法糖。它是建立在 “函数是 Julia 语言中的第一等公民” 这一特性的基础之上的。
就像我们在先前所展示的那样,当一个函数需要另一个函数作为其第一个参数值的时候,do
代码块就能够派上用场了:
julia> setprecision(35) do
BigFloat(1.01) + parse(BigFloat, "0.2")
end
1.2099999999
这里调用的函数 Base.MPFR.setprecision
的签名是这样的:
setprecision(f::Function, [T=BigFloat,] precision::Integer)
它的第一个参数是 Function
类型的,即代表函数的数据类型。
为了完整的演示,我们需要先稍微改造一下之前定义过的函数 map1
。如下所示:
julia> function map1(f::Function, vec::Vector)
[f(e) for e in vec]
end
map1 (generic function with 1 method)
julia>
在这里,f
变成了 map1
函数的第一个参数。又由于可选的位置参数只能被排在位置参数列表的最后,所以 f
在是必选的参数。另外,我还为 f
参数声明了类型。
现在,我们可以像下面这样调用改造后的 map1
函数:
julia> map1(e->e*10, [1,2,3,4])
4-element Array{Int64,1}:
10
20
30
40
julia>
或者,在调用它的时候携带一个 do
代码块:
julia> map1([1,2,3,4]) do x
x*10
end
4-element Array{Int64,1}:
10
20
30
40
julia>
我们这次一共向 REPL 环境输入了三行代码。第一行代码包括针对 map1
函数的调用表达式 map1([1,2,3,4])
、关键字 do
以及标识符 x
。实际上,后两者与第二行的表达式 x*10
和第三行的关键字 end
共同组成了一个 do
代码块。
请注意,虽然 map1
函数的必选参数有两个,但我们只在调用表达式中传给了它一个参数值。你应该也看出来了,被传入的这个参数值是给该函数的第二个参数的。那么,第一个参数值在哪里呢?
答案是,我们这次传给 map1
函数的第一个参数值就是那个 do
代码块。我们可以把 do
代码块看成函数定义的一种变体。一个 do
代码块就代表了一个匿名函数。在这里,处于 do
关键字右边的标识符 x
就相当于函数定义中的一个参数声明。
不过,这有一个限制,那就是:do
代码块代表的匿名函数只能有一个参数。当然了,我们可以通过一些手段突破这个限制。为了加以说明,我们再来定义一个 map1
方法:
julia> function map1(f::Function, vec::Vector, extra)
[f((e, extra)) for e in vec]
end
map1 (generic function with 2 methods)
julia>
这个 map1
方法有三个参数。参数 extra
代表了额外的附加值。另外,这个方法传给 f
的参数值是元组 (e, extra)
,而不是之前的单一变量 e
。相应的,我们调用该方法时所携带的 do
代码块也需要有所变化:
julia> map1([1,2,3,4], 1) do (x, y)
x*10+y
end
4-element Array{Int64,1}:
11
21
31
41
julia>
可以看到,我们在这里的调用表达式中向这个 map1
方法传入了第二个参数值 [1,2,3,4]
和第三个参数值 1
,而第一个参数值仍然由后面的 do
代码块代表。但不同的是,在 do
关键字右边的是由一个圆括号包裹的两个标识符。你也可以把这一小段代码看成一个元组。因为它表示的只是一个参数,而不是两个。这也正是这个 map1
方法在调用 f
时传入 (e, extra)
而非 e
和 extra
的原因。
do
代码块的意义在于,当我们需要临时定义一个函数并把它作为第一个参数值传入另一个函数的时候,使用 do
代码块会让代码变得非常的清晰。因为它看起来就是(事实上也是)一个独立的代码块。我们可以在这样的代码块中写入各种复杂的逻辑,而丝毫不会对前面的调用表达式以及周边的代码造成视觉上的干扰。如果在这种情况下不使用 do
代码块,那么就很可能会降低相关代码的可读性,甚至会间接地导致一些代码编写方面的错误。由此看来,do
代码块在特定的场景下是很有用处的。