匿名函数用 (参数) => 表达式——单个不带注解的参数可省括号,写成 x => 表达式;
类型可推导时参数注解可省。函数类型写作
fn(A) -> B !e,其中 !e 是效果。纯函数看签名即知没有副作用,测试无需 mock。
pub fn main() -> Unit !io = {
let nums = [1, 2, 3, 4]
let evens = filter(nums, n => n % 2 == 0)
let doubled = map(evens, n => n * 2)
println(to_string(doubled))
}
[4, 8]
高阶函数用效果变量转发参数的效果:map(f) 的效果等于 f 的效果。
两个函数参数的效果之并写作 !(e1 | e2)——纯 ∘ 纯还是纯,沾 io 便是 io。
fn compose[A, B, C](f: fn(A) -> B !e1, g: fn(B) -> C !e2) -> fn(A) -> C !(e1 | e2) =
a => g(f(a))
fn inc(x: Int) -> Int = x + 1
fn dbl(x: Int) -> Int = x * 2
pub fn main() -> Unit !io = {
let f = compose(inc, dbl)
println(to_string(f(10)))
}
22
块作为最后一个实参#
调用的最后一个实参是函数时,可以写成紧跟在调用后面、同一行上的一个裸块:
f(a) { e } 就是 f(a, () => e)。带参数的块像 lambda 那样在 => 前写出参数,
一个裸名字,或者一个带括号的列表:
fn twice(body: fn() -> Unit !e) -> Unit !e = {
body()
body()
}
pub fn main() -> Unit !io = {
twice {
println("hi")
}
let total = fold([1, 2, 3], 0) { (acc, x) => acc + x * x }
println("${total}")
let loud = map(["a", "b"]) { s => s ++ "!" }
println("${loud}")
}
hi
hi
14
["a!", "b!"]
不管前面传了什么,块填的都是最后一个形参,所以 fold(xs, 0) { ... } 读起来就是
「从 0 开始 fold,用这个」。块必须和调用在同一行开头;下一行的 { 是另一条块语句。
在 if、while、for 的头部以及 match 后面,花括号是语句体而不是实参,那里要给
调用加括号:if (f(x) { ... }) { ... }。配上具名实参与默认值,一个选项很多、又带一段
主体的函数在调用点就读作 column(gap: 12) { ... }。
另见:spec.md §4.3、§4.5、§6