改变世界的编程语言 MoonBit
大家好,我是农村程序员,独立开发者,前端之虎陈随易,我的个人网站是
https://chensuiyi.me
这是我的
《改变世界的编程语言MoonBit》系列文章,将自己学习和理解 MoonBit 的过程分享给大家,希望能带来参考和帮助。
全部文章可以前往 MoonBit 开发网
https://moonbit.edgeone.app或https://moonbit.pages.dev查看,我坚信,MoonBit 将会改变世界。
往期文章
- 改变世界的编程语言 MoonBit:背景知识速览
- 改变世界的编程语言 MoonBit:项目文件详解
- 改变世界的编程语言 MoonBit:配置系统介绍(上)
- 改变世界的编程语言 MoonBit:配置系统介绍(下)
- 改变世界的编程语言 MoonBit:如何用于前端开发
前面几篇文章,我们系统地学习了 MoonBit 的背景知识、项目结构、配置系统以及如何在前端开发中使用。今天,我们终于要进入 MoonBit 语言本身的语法学习了!
本文将带你全面了解 MoonBit 的基础语法,包括程序结构、变量绑定、数据类型、函数定义、控制流等核心概念。让我们一起开始这段精彩的语法之旅吧!
程序的基本组成
一个 MoonBit 程序由以下顶层定义组成:
- 类型定义:定义结构体、枚举等数据类型
- 函数定义:定义可复用的代码块
- 常量定义和变量绑定:定义不变的常量和可变的变量
init函数:初始化函数main函数:程序主入口test块:内联测试
类比理解:
如果把 MoonBit 程序比作一本书,那么:
- 类型定义就像是书中的角色设定
- 函数定义就像是各个章节
main函数就是故事的开头test块就是每章后面的习题
表达式和语句
MoonBit 区分语句和表达式,这是理解 MoonBit 的关键。
表达式:会产生一个值,包括:
- 值字面量(布尔值、数字、字符、字符串、数组、元组、结构体)
- 算术、逻辑或比较操作
- 访问数组元素(如
a[0])、结构体字段(如r.x)、元组组件(如t.0) - 变量和枚举构造器
- 匿名函数定义
match、if、loop表达式等
语句:执行某些操作但不产生值,包括:
- 命名本地函数定义
- 本地变量绑定
- 赋值
return语句- 任何返回类型为
Unit的表达式
重要规则:在函数体中,只有最后一个子句应该是一个表达式,它作为返回值。
fn foo() -> Int {
let x = 1 // 语句:变量绑定
x + 1 // 表达式:作为返回值
}
通俗理解:
语句就像做事情,比如"把苹果放桌上";表达式就像回答问题,比如"桌上有几个苹果?答:3 个"。
变量绑定
MoonBit 支持三种变量绑定方式:
不可变变量(let)
使用 let 声明的变量不能被重新赋值:
fn main {
let x = 10
// x = 20 // ❌ 错误!不可变变量不能重新赋值
println(x)
}
可变变量(let mut)
使用 let mut 声明的变量可以被重新赋值:
fn main {
let mut i = 10
i = 20 // ✅ 正确!可变变量可以重新赋值
println(i)
}
常量(const)
使用 const 声明的常量只能在顶层定义,不能在函数内部定义:
const ZERO = 0 // ✅ 顶层常量
fn main {
// const LOCAL = 1 // ❌ 错误!常量不能在函数内定义
println(ZERO)
}
注意事项:
- 顶层变量绑定需要显式类型注释(除非使用字符串、字节或数字等字面量定义)
- 顶层变量不能是可变的(如需可变,使用
Ref代替)
命名约定
MoonBit 有一套清晰的命名规范:
| 类型 | 规则 | 示例 |
|---|---|---|
| 变量、函数 | 小写字母开头,建议 snake_case | my_var、calculate_sum |
| 常量、类型 | 大写字母开头,建议 PascalCase 或 SCREAMING_SNAKE_CASE | MyType、MAX_SIZE |
关键字(不能使用)
as, else, extern, fn, fnalias, if, let, const, match, using,
mut, type, typealias, struct, enum, trait, traitalias, derive,
while, break, continue, import, return, throw, raise, try, catch,
pub, priv, readonly, true, false, _, test, loop, for, in, impl,
with, guard, async, is, suberror, and, letrec, enumview, noraise, defer
保留关键字(会引发警告)
module, move, ref, static, super, unsafe, use, where, await,
dyn, abstract, do, final, macro, override, typeof, virtual, yield...
通俗理解:
命名规范就像给孩子取名字,有些名字(关键字)是被语言"占用"的,你不能用;有些名字虽然目前可以用,但可能将来会被占用(保留关键字)。
程序入口
init 函数
init 函数是特殊的初始化函数:
- 没有参数列表也没有返回类型
- 同一个包中可以有多个
init函数 - 不能被显式调用或被其他函数引用
- 在初始化包时会隐式自动调用
fn init {
let x = 1
println(x)
}
main 函数
main 函数是程序的主入口,在初始化阶段之后执行:
fn main {
let x = 2
println(x)
}
运行以上两段代码,输出:
1
2
注意:只有标记为 is-main: true 的包才能定义 main 函数。
test 块
test 块用于定义内联测试:
test "test_name" {
assert_eq(1 + 1, 2)
assert_eq(2 + 2, 4)
inspect([1, 2, 3], content="[1, 2, 3]")
}
通俗理解:
init就像开餐厅前的准备工作(备菜、摆桌子)main就像正式开门营业test就像品控检查,确保菜品质量
内置数据类型
MoonBit 提供了丰富的内置数据类型:
Unit 类型
Unit 表示没有有意义的值,类似于 C/C++ 的 void,但它是一个真正的类型:
fn print_hello() -> Unit {
println("Hello, world!")
}
布尔值
布尔类型有两个值:true 和 false:
let a = true
let b = false
let c = a && b // 逻辑与
let d = a || b // 逻辑或
let e = not(a) // 逻辑非
数字类型
MoonBit 支持多种数字类型:
| 类型 | 说明 | 示例 |
|---|---|---|
Int16 | 16 位有符号整数 | (42 : Int16) |
Int | 32 位有符号整数 | 42 |
Int64 | 64 位有符号整数 | 1000L |
UInt16 | 16 位无符号整数 | (14 : UInt16) |
UInt | 32 位无符号整数 | 14U |
UInt64 | 64 位无符号整数 | 14UL |
Double | 64 位浮点数 | 3.14 |
Float | 32 位浮点数 | (3.14 : Float) |
BigInt | 大整数 | 10000000000000000000000N |
数字字面量支持多种进制:
let decimal = 1_000_000 // 十进制,支持下划线分隔
let binary = 0b110010 // 二进制
let octal = 0o1234 // 八进制
let hex = 0xA_B_C // 十六进制
类型自动重载:
let int : Int = 42
let double : Double = 42 // 自动转换为 42.0
let bigint : BigInt = 42 // 自动转换为大整数
字符串
字符串包含一系列 UTF-16 码点:
let a = "兔rabbit"
println(a[0].to_char()) // Some('兔')
println(a[1].to_char()) // Some('r')
多行字符串:
使用 #| 前缀保留原始字符串,使用 $| 前缀支持转义和插值:
let lang = "MoonBit"
let b =
#| Hello
#| ---
$| \{lang}\n
#| ---
字符串插值:
let x = 42
println("The answer is \{x}") // The answer is 42
转义序列:
| 转义序列 | 含义 |
|---|---|
\n | 换行 |
\r | 回车 |
\t | 制表符 |
\b | 退格 |
\\ | 反斜杠 |
\u5154 | Unicode 转义(4 位) |
\u{1F600} | Unicode 转义(可变长) |
字符
Char 表示一个 Unicode 码点:
let a : Char = 'A'
let b = '兔'
let zero = '\u0030' // '0'
let emoji = '\u{1F600}' // 😀
当期望类型是 Int 时,字符字面量可以重载为 Int 类型:
let s : String = "hello"
let diff = s[0] - 'a' // 字符 'a' 被重载为 Int
字节
字节字面量是一个 ASCII 字符或转义序列:
let b1 : Byte = b'a'
let b2 = b'\xff'
// 字节序列
let bs : Bytes = b"abcd"
元组
元组是使用圆括号构造的有限值集合:
fn pack(a : Bool, b : Int, c : String) -> (Bool, Int, String) {
(a, b, c)
}
fn main {
let quad = pack(false, 100, "text")
let (bool_val, int_val, str) = quad // 解构
println("\{bool_val} \{int_val} \{str}")
// 也可以通过索引访问
let x = quad.0 // false
let y = quad.1 // 100
}
数组
数组是使用方括号构造的有限值序列:
let numbers = [1, 2, 3, 4]
fn main {
let a = numbers[2] // 访问第3个元素
numbers[3] = 5 // 修改第4个元素
println(a + numbers[3]) // 8
}
两种数组类型:
Array[T]:可增长的数组FixedArray[T]:固定大小的数组
ArrayView(数组视图):
let xs = [0, 1, 2, 3, 4, 5]
let s1 = xs[2:] // [2, 3, 4, 5]
let s2 = xs[:4] // [0, 1, 2, 3]
let s3 = xs[2:5] // [2, 3, 4]
Map(映射)
let map : Map[String, Int] = { "x": 1, "y": 2, "z": 3 }
Option 和 Result
用于表示可能的错误或失败:
let a : Int? = None // 没有值
let b : Option[Int] = Some(42) // 有值
let c : Result[Int, String] = Ok(42) // 成功
let d : Result[Int, String] = Err("error") // 失败
Ref(可变引用)
Ref[T] 是包含类型 T 的值的可变引用:
let a : Ref[Int] = { val: 100 }
fn main {
a.val = 200
println(a.val) // 200
}
函数定义
顶层函数
使用 fn 关键字定义顶层函数,需要显式类型注释:
fn add3(x : Int, y : Int, z : Int) -> Int {
x + y + z
}
局部函数
局部函数可以是命名的或匿名的,类型可以自动推断:
fn main {
// 命名局部函数
fn inc(x) {
x + 1
}
// 匿名函数
let double = fn(x) { x * 2 }
// 箭头函数(更简洁)
[1, 2, 3].each(x => println(x * x))
println(inc(5)) // 6
println(double(5)) // 10
}
函数应用
add3(1, 2, 7) // 10
// 任何求值为函数值的表达式都是可应用的
let w = (if true { fn(x) { x + 1 } } else { fn(x) { x + 2 } })(3)
// w = 4
部分应用
使用 _ 操作符进行部分应用:
fn add(x : Int, y : Int) -> Int {
x + y
}
fn main {
let add10 = add(10, _) // 创建一个新函数
println(add10(5)) // 15
println(add10(10)) // 20
}
带标签的参数
fn labelled(arg1~ : Int, arg2~ : Int) -> Int {
arg1 + arg2
}
fn main {
let arg1 = 1
// 可以使用 label=value 或 label~ 简写
println(labelled(arg2=2, arg1~)) // 3
}
可选参数
fn optional(opt? : Int = 42) -> Int {
opt
}
fn main {
println(optional()) // 42(使用默认值)
println(optional(opt=0)) // 0
}
没有默认值的可选参数
没有默认值的可选参数类型为 T?,默认值为 None。调用时 MoonBit 会自动用 Some 包装:
fn new_image(width? : Int, height? : Int) -> Unit {
if width is Some(w) {
println("width: \{w}")
} else {
println("no width")
}
}
fn main {
new_image(width=1920, height=1080)
}
转发可选参数时,使用 label?=value 语法,label? 是 label?=label 的简写:
fn fixed_width_image(height? : Int) -> Unit {
new_image(width=1920, height?) // 转发 height 参数
}
控制结构
条件表达式(if)
let initial = if size < 1 { 1 } else { size }
注意:条件表达式在 MoonBit 中始终返回一个值,结果和 else 子句的返回值必须是相同的类型。
匹配表达式(match)
fn decide_sport(weather : String) -> String {
match weather {
"sunny" => "tennis"
"rainy" => "swimming"
_ => "unknown" // 默认情况
}
}
卫语句(guard)
用于检查指定的不变量:
fn guarded_get(array : Array[Int], index : Int) -> Int? {
guard index >= 0 && index < array.length() else { None }
Some(array[index])
}
结合 is 表达式使用:
fn getProcessedText(resources : Map[String, Resource], path : String) -> String {
guard resources.get(path) is Some(resource) else {
fail("\{path} not found")
}
guard resource is PlainText(text) else {
fail("\{path} is not plain text")
}
process(text)
}
While 循环
fn main {
let mut i = 5
while i > 0 {
println(i)
i = i - 1
}
}
// 输出: 5 4 3 2 1
支持 break 和 continue:
fn main {
let mut i = 5
while i > 0 {
i = i - 1
if i == 4 { continue } // 跳过本次
if i == 1 { break } // 退出循环
println(i)
}
}
// 输出: 3 2
支持 else 子句:
fn main {
let mut i = 2
while i > 0 {
println(i)
i = i - 1
} else {
println("循环结束,i = \{i}")
}
}
// 输出: 2 1 循环结束,i = 0
For 循环
C 风格的 for 循环:
fn main {
for i = 0; i < 5; i = i + 1 {
println(i)
}
}
// 输出: 0 1 2 3 4
多变量绑定:
for i = 0, j = 0; i + j < 100; i = i + 1, j = j + 1 {
println(i)
}
for..in 循环
遍历数据结构的便捷语法:
for x in [1, 2, 3] {
println(x)
}
遍历整数范围:
for j in 0..<10 { // 0 到 9
println(j)
}
for l in 0..=10 { // 0 到 10(包含)
println(l)
}
遍历 Map:
for k, v in { "x": 1, "y": 2, "z": 3 } {
println("\{k}: \{v}")
}
遍历数组(带索引):
for index, elem in [4, 5, 6] {
println("第 \{index + 1} 个元素是 \{elem}")
}
函数式循环(loop)
fn sum(xs : @list.List[Int]) -> Int {
loop (xs, 0) {
(Empty, acc) => break acc
(More(x, tail=rest), acc) => continue (rest, x + acc)
}
}
defer 表达式
用于可靠的资源释放:
fn main {
defer println("释放资源")
println("使用资源")
}
// 输出:使用资源
// 释放资源
连续的 defer 以倒序执行:
fn main {
defer println("第一处 defer")
defer println("第二处 defer")
println("做些事情")
}
// 输出:做些事情
// 第二处 defer
// 第一处 defer
自定义数据类型
结构体(struct)
struct User {
id : Int
name : String
mut email : String // 可变字段
}
fn main {
let u = User::{ id: 0, name: "John Doe", email: "john@doe.com" }
u.email = "john@doe.name" // 修改可变字段
println(u.name)
}
简写构造:
let name = "john"
let email = "john@doe.com"
let u = { id: 0, name, email } // 字段名与变量名相同时可简写
结构体更新语法:
let user = { id: 0, name: "John", email: "john@doe.com" }
let updated = { ..user, email: "new@email.com" } // 只更新 email
枚举(enum)
enum Relation {
Smaller
Greater
Equal
}
fn compare_int(x : Int, y : Int) -> Relation {
if x < y { Smaller }
else if x > y { Greater }
else { Equal }
}
带数据的枚举:
enum Lst {
Nil
Cons(Int, Lst) // 携带额外数据
}
fn is_singleton(l : Lst) -> Bool {
match l {
Cons(_, Nil) => true
_ => false
}
}
带标签参数的构造器:
enum Point {
Point2D(x~ : Int, y~ : Int)
}
fn main {
let p = Point2D(x=10, y=20)
match p {
Point2D(x~, y~) => println("x=\{x}, y=\{y}")
}
}
元组结构体
struct UserId(Int)
struct UserInfo(UserId, String)
fn main {
let id = UserId(1)
let info = UserInfo(id, "John")
// 通过索引访问
println(id.0) // 1
println(info.1) // John
}
类型别名
pub type MyIndex = Int
pub type MyMap = Map[Int, String]
模式匹配
简单模式
const ONE = 1
fn match_int(x : Int) -> Unit {
match x {
0 => println("zero")
ONE => println("one")
value => println(value) // 绑定到变量
}
}
使用 _ 作为通配符,.. 忽略剩余字段:
struct Point3D { x : Int, y : Int, z : Int }
fn match_point(p : Point3D) -> Unit {
match p {
{ x: 0, .. } => println("在 yz 平面上")
_ => println("不在 yz 平面上")
}
}
数组模式
fn match_array(arr : Array[Int]) -> Unit {
match arr {
[] => println("空数组")
[a, b, c] => println("三元素: \{a}, \{b}, \{c}")
[a, .. rest, b] => println("首: \{a}, 尾: \{b}")
}
}
范围模式
fn classify_char(c : Char) -> String {
match c {
'a'..='z' => "lowercase"
'A'..='Z' => "uppercase"
'0'..='9' => "digit"
_ => "other"
}
}
Map 模式
fn match_map(map : Map[String, Int]) -> Unit {
match map {
{ "b": _, .. } => println("包含 key 'b'")
{ "a": x, .. } => println("a 的值是 \{x}")
_ => println("其他情况")
}
}
守卫条件
fn guard_example(x : Int?) -> Int {
match x {
Some(a) if a >= 0 => a
Some(b) => -b
None => -1
}
}
泛型
泛型在顶层函数和数据类型定义中受支持:
enum List[T] {
Nil
Cons(T, List[T])
}
fn[S, T] List::map(self : List[S], f : (S) -> T) -> List[T] {
match self {
Nil => Nil
Cons(x, xs) => Cons(f(x), xs.map(f))
}
}
fn[S, T] List::reduce(self : List[S], op : (T, S) -> T, init : T) -> T {
match self {
Nil => init
Cons(x, xs) => xs.reduce(op, op(init, x))
}
}
特殊语法
管道操作符(|>)
5 |> ignore // 等价于 ignore(5)
[] |> Array::push(5) // 等价于 Array::push([], 5)
1 |> add(5) |> ignore // 链式调用
级联运算符(..)
用于连续对同一值执行多个操作:
let result = StringBuilder::new()
..write_char('a')
..write_char('b')
..write_string("cdef")
.to_string()
is 表达式
测试值是否符合特定模式:
fn is_none[T](x : T?) -> Bool {
x is None
}
fn start_with_lower(s : String) -> Bool {
s is ['a'..='z', ..]
}
// 在 if 中使用
fn process(x : Array[Int?]) -> Unit {
if x is [v, ..] && v is Some(i) && i is (0..=10) {
println("匹配成功:\{i}")
}
}
展开运算符(..)
在构造数组时展开元素序列:
let a1 = [1, 2, 3]
let a2 = [4, 5, 6]
let combined : Array[Int] = [..a1, ..a2, 7, 8]
// [1, 2, 3, 4, 5, 6, 7, 8]
TODO 语法(...)
用于标记尚未实现的代码:
fn todo_func() -> Int {
... // 占位符,表示待实现
}
迭代器
MoonBit 内置了 Iter[T] 类型,是一种内部迭代器:
fn filter_even(l : Array[Int]) -> Array[Int] {
l.iter()
.filter(x => (x & 1) == 0)
.collect()
}
fn fact(n : Int) -> Int {
(1).until(n).fold(Int::mul, init=1)
}
常用方法:
each:遍历每个元素fold:折叠操作collect:收集到数组filter:过滤元素map:转换元素concat:合并两个迭代器
优势:filter、map 等方法是惰性的,不会为中间值分配内存。
总结
本文介绍了 MoonBit 语言的基础语法,包括:
| 概念 | 要点 |
|---|---|
| 程序结构 | 由类型定义、函数定义、变量绑定、init/main/test 组成 |
| 变量绑定 | let(不可变)、let mut(可变)、const(常量) |
| 数据类型 | Unit、布尔、数字、字符串、数组、元组、Map、Option、Result 等 |
| 函数 | 顶层函数、局部函数、匿名函数、标签参数、可选参数 |
| 控制流 | if、match、guard、while、for、for..in、loop、defer |
| 自定义类型 | struct、enum、元组结构体、类型别名 |
| 模式匹配 | 简单模式、数组模式、范围模式、Map 模式、守卫条件 |
| 泛型 | 类型参数化的函数和数据类型 |
| 特殊语法 | 管道 |>、级联 ..、is 表达式、展开运算符 |
MoonBit 融合了函数式和命令式编程的优点,语法简洁优雅,类型系统强大。掌握这些基础语法后,你就可以开始编写真正的 MoonBit 程序了!