let 绑定不可变,var 可变。基本类型有 Int、Float、Bool、String。
顶层函数必须写全参数类型与返回类型——签名即契约。
fn square(x: Int) -> Int = x * x
fn abs(x: Int) -> Int =
if x < 0 { 0 - x } else { x }
pub fn main() -> Unit !io = {
var total = 0
total = total + square(3)
total = total + abs(-4)
println(to_string(total))
}
13
点调用:x.f(a) 就是 f(x, a)#
函数主要作用于它的第一个参数时,就把这个参数写在前面。x.f(a) 与 f(x, a) 完全等价:
接收者填第一个形参,括号里是其余实参。点调用连成链,读起来是数据的流向;嵌套调用则要由内向外读:
fn double(x: Int) -> Int = x * 2
fn inc(x: Int) -> Int = x + 1
pub fn main() -> Unit !io = {
let n = 5
n.double().inc().to_string().println()
let total = [1, 2, 3, 4]
.filter(x => x > 1)
.map(double)
.fold(0, (acc, x) => acc + x)
println(total.to_string())
}
11
18
对同一个值的两步及以上操作一律写成链,不写嵌套调用:写 e.insert(5).insert(2),
不写 insert(insert(e, 5), 2)。链太长就一步一行,续行以 . 开头。没有天然主语的调用保持普通写法:
构造器(Node(l, v, r))、range(0, n)、println("hi"),以及 max(a, b) 这类对称函数。
点调用只找得到以裸名在作用域内的函数:你自己的函数、prelude 的 builtin、选择性引入的名字。
只写 use std/str 时,s.split(",") 是错误,报错会提示写 str.split(s, ","),
或加上 use std/str.{split}。完整的解析顺序见 spec §4.3。
管道 |>#
管道是同一个调用的另一种拼写:x |> f(a) 就是 f(x, a)。点写法不合适时用它:数据起点是字面量或较大的表达式、
最后一步是汇或 lambda、函数带模块限定而你不想引入它的裸名(s |> str.split(",")),
或者右侧是函数值而不是调用。它的优先级比其余运算符都低,所以 a + b |> f 是 f(a + b)。
一条链要么全用点,要么全用管道,不混写。
fn double(x: Int) -> Int = x * 2
fn inc(x: Int) -> Int = x + 1
pub fn main() -> Unit !io =
5 |> double |> inc |> to_string |> println
11
具名实参与默认值#
形参可以带默认值,写作 name: Type = expr,调用时就可以省掉这个实参。任何实参也都可以
按名字传:位置实参从左往右占位,具名实参占它点名的那一格,名字之间的先后随意。
fn greet(name: String, greeting: String = "Hello", punct: String = "!") -> String =
"${greeting}, ${name}${punct}"
pub fn main() -> Unit !io = {
println(greet("Dawn"))
println(greet("Dawn", punct: "?"))
println(greet(punct: ".", name: "reader", greeting: "Welcome"))
}
Hello, Dawn!
Hello, Dawn?
Welcome, reader.
默认值在每次省掉它的调用里重新求值,而且必须是纯的:要是它能做 io,一次调用做不做 io
就取决于调用方传没传这个实参。实参按书写顺序求值,不管它们落在哪一格。名字属于签名,
所以函数值没有名字:let g = greet 之后,g(name: "x") 是错误,g 要求三个实参一个
不少。构造器也一样收名字,字段名就是它的形参名(Rect(w: 3.0, h: 4.0))。
另见:spec.md §2.1、§3.1、§4.3、§4.4