Julia 鼓励程序员尽可能把代码放进能被 JIT 编译的函数里,而且按设计,创建大量小函数本身就是高效的。
这样一来,程序里往往就剩下许多小而简单的函数,而要完成不那么简单的任务,就需要把它们组合起来。
一种显而易见的做法是嵌套调用函数。下面这个例子非常刻意,但能说明问题。
julia> first.(titlecase.(reverse.(["my", "test", "strings"])))
3-element Vector{Char}:
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
这种做法的缺点在于,嵌套一深,可读性就会迅速下降。
我们需要一种更简单、更灵活的做法。
这是数学家们钟爱的技巧,Julia 也照搬了数学里的写法。
任意数量的函数都可以用 ∘ 运算符composed在一起(输入 \circ 再按 Tab 键)。结果可以当作一个函数来使用。
julia> compfunc = first ∘ titlecase ∘ reverse
first ∘ titlecase ∘ reverse
julia> compfunc.(["my", "test", "strings"])
3-element Vector{Char}:
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
# alternative syntax, giving the same result
julia> (first ∘ titlecase ∘ reverse).(["my", "test", "strings"])
有几点需要注意:
另一种思路可以看作是_程序员_的做法,而不是_数学家_的做法。
Pipelines在 Unix shell 脚本里已经用了很久,近几年又在主流编程语言中流行起来(有时人们认为 F# 是最早采用这种写法的语言)。
基本思路是:先有一份数据,然后让它依次经过一串函数,从而得到结果。
管道运算符是 |>(F# 和较新版本的 R 都是这样),不过 Julia 还有一个广播版本 .|>。
julia> ["my", "test", "strings"] .|> reverse .|> titlecase .|> first
3-element Vector{Char}:
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
现在执行顺序严格从左到右,每个函数的输出沿着箭头方向流动,成为下一个函数的输入。
用来演示组合和管道的函数都只接受_一个_参数,这并非巧合。
有些纯函数式语言会把_第一个_参数送入函数,同时允许再传入其他参数。
相比之下,Julia 的管道里只接受函数的_名字_(或与之等价的东西),不能附带其他参数。
这背后有重要的技术原因(与currying并不是这门语言设计中的标准组成部分有关)。那些完全不了解柯里化的_众多_人,只需接受一点:这个限制并非粗心造成的疏忽,也不太可能在未来的 Julia 版本中改变。
我们需要的是只接受单个参数、又能完成所需工作的函数。幸运的是,在 Julia 里定义新函数很容易。
最简单的方式是使用anonymous function。例如,假设有一个输入字符串,我们想按下划线来拆分它:
julia> "my_test_strings" |> (s -> split(s, '_'))
3-element Vector{SubString{String}}:
"my"
"test"
"strings"
之后就可以像之前那样,把这个数组传给其他函数。
在这个例子里,把匿名函数括在括号里是可选的,但一般来说,这是减少歧义的好办法。
同样,我们也可以在程序较前面的位置定义一个具名函数,需要时重复使用。
在这个练习中,你将帮助高中情侣在社交媒体上表达爱意,方法是生成一个用两人首字母组成的 Unicode 心形:
❤ J. + M. ❤
实现cleanupname方法。
它应接受一个名字,把其中所有的-字符替换为空格。
还应该去掉名字开头和结尾的空白。
julia> cleanupname("Jane-Ann")
"Jane Ann"
实现firstletter方法。
它应接受一个名字,并以字符串的形式返回其首字母。
记得复用你在上一步定义的cleanupname,并把它与其他函数组合起来。
julia> firstletter("Jane")
"J"
实现initial方法。
它应接受一个名字,返回其首字母的大写形式,后面跟一个点。
记得复用你在上一步定义的firstletter。
initial("Robert")
"R."
实现couple方法。
它应接受两个名字,返回两边带有爱心表情的缩写。
记得复用你在上一步定义的initial。
couple("Blake Miller", "Riley Lewis")
"❤ B. + R. ❤"