MoonBit
MoonBit 是一个用于云计算和边缘计算的 WebAssembly 端到端的编程语言工具链。 您可以访问 https://try.moonbitlang.cn 获得 IDE 环境,无需安装任何软件,也不依赖任何服务器。
状态
MoonBit 目前处于 Pre-alpha 阶段,是实验性质的。我们期望今年能达到 beta 阶段。
主要优势
- 生成比现有解决方案明显更小的 WASM 文件。
- 更高的运行时性能。
- 先进的编译时性能。
- 简单且实用的数据导向语言设计。
概述
一个月兔程序由类型定义,函数定义和变量绑定组成。
每个包的入口点是一个特殊的 init
函数,它有以下两个特点:
- 同一个包中可以有多个
init
函数。 init
函数不能被显式调用或被其他函数引用。相反,在一个包初始化时,所有的init
函数都将被隐式地调用。因此,init
函数中只能包含语句。
fn init {
print("Hello world!") // OK
}
fn init {
let x = 1
// x // 失败
print(x) // 成功
}
MoonBit 区分语句和表达式。在一个函数体中,只有最后一句才能写成作为返回值的表达式。例如:
fn foo() -> Int {
let x = 1
x + 1 // OK
}
fn bar() -> Int {
let x = 1
x + 1 // 失败
x + 2
}
fn init {
print(foo())
print(bar())
}
表达式和语句
表达式包括:
- 值字面量(例如布尔值、数字、字符、字符串、数组、元组、结构体)
- 算术、逻辑和比较运算
- 访问数组元素(例如
a[0]
)、结构体字段(例如r.x
)或元组的元素(例如t.0
) - 变量 和(大写字母开头的)枚举构造器
- 匿名局部函数定义
match
和if
表达式
语句包括:
- 命名局部函数定义
- 局部变量绑定
- 赋值
while
循环和相关的控制流结构(break
和continue
)return
语句- 返回类型为
unit
的任何表达式
函数
函数接受参数并产生结果。 在 MoonBit 中,函数是一等公民,这意味着函数可以作为其他函数的参数或返回值。
顶层函数
函数可以被定义为顶层或局部。
我们可以使用 fn
关键字定义一个顶层函数,
例如以下函数求三个整数之和并返回结果:
fn add3(x: Int, y: Int, z: Int)-> Int {
x + y + z
}
注意,顶层函数的参数和返回值类型需要显式标注。
局部函数
局部函数使用 fn
关键字定义。局部函数可以是命名的或匿名的。在大多数情况下,局部函数的类型注解可以省略,因为编译器可以自动推断。例如:
fn foo() -> Int {
fn inc(x) { x + 1 } // 命名为 `inc`
fn (x) { x + inc(2) } (6) // 匿名,立即应用到整数字面量 6
}
fn init {
print(foo())
}
无论是命名的还是匿名的,函数都是 词法闭包:任何没有局部绑定的标识符, 必须引用来自周围词法作用域的绑定:
let y = 3
fn foo(x: Int) -> Unit {
fn inc() { x + 1 } // OK,返回 x + 1
fn four() { y + 1 } // Ok,返回 4
print(inc())
print(four())
}
fn init {
foo(2)
}
函数调用
函数可通过向圆括号内传入参数列表进行调用:
add3(1, 2, 7)
这适用于命名函数(如前面的例子)和绑定到函数值的变量,如下所示:
fn init {
let add3 = fn(x, y, z) { x + y + z }
print(add3(1, 2, 7))
}
表达式 add3(1, 2, 7)
返回 10
。任何求值为函数值的表达式都可以被调用:
fn init {
let f = fn (x) { x + 1 }
let g = fn (x) { x + 2 }
print((if true { f } else { g })(3)) // OK
}
带标签的参数
可以用 ~label : Type
的语法为函数声明带标签的参数。函数体内参数的名字也是 label
:
fn labelled(~arg1 : Int, ~arg2 : Int) -> Int {
arg1 + arg2
}
调用函数时,可以用 label=arg
的语法提供带标签的参数。label=label
可以简写成 ~label
:
fn init {
let arg1 = 1
println(labelled(arg2=2, ~arg1)) // 3
}
可以用任意的顺序提供带标签的参数。参数的求值顺序与函数声明中参数的顺序相同。
可选的参数
可选的参数是带有默认值的带标签参数。声明可选的参数的语法是 ~label : Type = default_expr
。调用函数时,如果没有提供这个参数,就会使用默认值作为参数:
fn optional(~opt : Int = 42) -> Int {
opt
}
fn init {
println(optional()) // 42
println(optional(opt=0)) // 0
}
每次使用默认参数调用一个函数时,都会重新求值默认值的表达式,也会被重新触发其中的副作用。例如:
fn incr(~counter : Ref[Int] = { val: 0 }) -> Ref[Int] {
counter.val = counter.val + 1
counter
}
fn init {
println(incr()) // 1
println(incr()) // 依然是 1,因为重新求值了默认表达式,产生了一个新的 Ref
let counter : Ref[Int] = { val: 0 }
println(incr(~counter)) // 1
println(incr(~counter)) // 2,因为两次调用使用了同一个 Ref
}
如果想要在多次不同的函数调用之间共享默认值,可以提前用 let
计算并保存默认值:
let default_counter : Ref[Int] = { val: 0 }
fn incr(~counter : Ref[Int] = default_counter) -> Int {
counter.val = counter.val + 1
counter.val
}
fn init {
println(incr()) // 1
println(incr()) // 2
}
默认值可以依赖于前面的参数,例如:
fn sub_array[X](xs : Array[X], ~offset : Int, ~len : Int = xs.length() - offset) -> Array[X] {
... // 生成 xs 的一个从 offset 开始、长度为 len 的子数组
}
fn init {
println(sub_array([1, 2, 3], offset=1)) // [2, 3]
println(sub_array([1, 2, 3], offset=1, len=1)) // [2]
}
自动填充的参数
MoonBit 能够自动在每次函数调用时填充某些特定类型的参数,例如函数调用在源码中的位置。要声明这种自动填充的参数,只需要使用 _
作为参数的默认值即可。如果在调用时没有提供这个参数,MoonBit 就会自动根据调用处的上下文填充这个参数。
目前 MoonBit 支持两种类型的自动填充参数。代表整个函数调用在源码中位置的 SourceLoc
类型,以及包含每个参数各自的位置的 ArgsLoc
类型:
fn f(_x : Int, _y : Int, ~loc : SourceLoc = _, ~args_loc : ArgsLoc = _) -> Unit {
println("整个函数调用的位置:\(loc)")
println("各个参数的位置:\(args_loc)")
}
fn init {
f(1, 2)
// 整个函数调用的位置:<文件名>:7:3-7:10
// 各个参数的位置:[Some(<文件名>:7:5-7:6), Some(<文件名>:7:8-7:9), None, None]
}
自动填充的参数可以用于编写调试和测试用的工具函数。
控制结构
条件表达式
条件表达式由条件、结果和一个可选的 else
子句组成。
if x == y {
expr1
} else {
expr2
}
if x == y {
expr1
}
else
子句也可以包含另一个 if-else
表达式:
if x == y {
expr1
} else if z == k {
expr2
}
花括号用于在结果或 else
子句中组合表达式。
注意,在 MoonBit 中,条件表达式总是返回一个值,其结果和 else
子句的返回值类型必须相同。
函数式循环
函数式循环是 MoonBit 中一个强大的特性,它能让您以函数式风格编写循环。
函数式循环接受参数并返回一个值。它使用 loop
关键字定义,后跟其参数和循环体。
循环体是一系列子句,每个子句由模式和表达式组成。
与输入匹配的模式会被执行,并且循环将返回表达式的值。如果没有匹配的模式,循环将抛出异常。
可以使用 continue
关键字和参数开始下一次循环迭代,使用 break
关键字和参数来从循环中返回一个值。
如果值是循环体中的最后一个表达式,则可以省略 break
关键字。
fn sum(xs: List[Int]) -> Int {
loop xs, 0 {
Nil, acc => break acc // break 可以省略
Cons(x, rest), acc => continue rest, x + acc
}
}
fn init {
println(sum(Cons(1, Cons(2, Cons(3, Nil)))))
}
循环
MoonBit 的 while
循环可以用如下语法定义:
while x == y {
expr1
}
while
语句不返回任何值;它只求值成 unit
类型的 ()
MoonBit 还提供 break
和 continue
语句来控制循环流。
let mut i = 0
let mut n = 0
while i < 10 {
i = i + 1
if (i == 3) {
continue
}
if (i == 8) {
break
}
n = n + i
}
// n = 1 + 2 + 4 + 5 + 6 + 7
println(n) // 输出 25
内置数据结构
布尔值
MoonBit 内置了布尔类型,它有两个值:true
和 false
。布尔类型用于条件表达式和控制结构。
let a = true
let b = false
let c = a && b
let d = a || b
let e = not(a)
数字
MoonBit 支持整型和浮点类型:
类型 | 描述 |
---|---|
Int | 32位有符号整数 |
Int64 | 64位有符号整数 |
Double | 64位浮点数,由IEEE754定义 |
MoonBit 支持的数字字面量,包括十进制、二进制、八进制和十六进制。
为了提升可读性,你可以在数字字面量内插入下划线,例如 1_000_000
。
注意,下划线可以插入到数字之间的任何位置,而非只能在每三个数字之间。
- 十进制数和往常一样。
let a = 1234
let b = 1_000_000 + a
let large_num = 9_223_372_036_854_775_807L // Int64 类型的整数必须后缀“L”
- 八进制数的前缀是 0 后接字母 O,也就是
0o
或0O
。注意在0o
或0O
之后出现的数字只能在0
到7
之间。
let octal = 0o1234
let another_octal = 0O1234
- 十六进制数的前缀是 0 后接字母 X,也就是
0x
或0X
。注意在0x
或0X
之后出现的数字只能是0123456789ABCDEF
之一。
let hex = 0XA
let another_hex = 0xA