

最近注意到Scala的市场在逐渐萎缩,这促使我探索其他具有类似特性的编程语言,例如支持函数式编程、高阶类型、高阶函数、泛型、运算符重载和领域建模等。
最近,我在 X(前称Twitter)上听说了MoonBit语言,并通过搜索了解了更多信息。MoonBit是一种AI原生的通用编程语言,由张宏波领导开发。
张宏波在编程语言开发方面有着丰富的经验,曾是 OCaml 的核心贡献者,ReScript的创建者,并在 Meta (前称 FaceBook)公司参与了 Flow 的开发。
MoonBit 由粤港澳大湾区数字经济学院(IDEA)开发,该机构致力于人工智能和数字经济领域的前沿研究和产业应用。
在其官方网站上,我发现MoonBit具有以下基本特性:
融合了Rust和Scala的优点不使用Rust中的“借用”概念采用垃圾回收机制性能卓越可编译为WebAssembly为了体验MoonBit的编程感受是否类似于编写高质量的Scala代码,我决定用MoonBit编写一些代码。我选择了一个众所周知的主题进行领域建模:国际象棋棋盘。我希望定义棋子、棋盘以及游戏的初始状态(暂不涉及棋子的移动和游戏逻辑)。
示例代码库可在以下链接找到:https://github.com/ignacio-hivemind/MoonBit-chess-example
接下来,让我们逐一探讨MoonBit的这些特性。
十大特性1、枚举类型
首先,我为棋盘上的棋子创建了一些定义。在国际象棋中,棋子可以是黑色或白色,种类包括兵(Pawn)、车(Rook)、马(Knight)、象(Bishop)、后(Queen)和王(King)。
// This application is about a chess board,// and how to represent it in the MoonBit language.//// Board is a double dimension array of BoardPlace// cols: 0 1 2 3 4 5 6 7// row 0: R N B Q K B N R// row 1: P P P P P P P P// row 2: . . . . . . . .// row 3: . . . . . . . .// row 4: . . . . . . . .// row 5: . . . . . . . .// row 6: p p p p p p p p// row 7: r n b q k b n r//// The upper case letters represent the white pieces,// whereas the lower case letters represent the black pieces.// The pieces are: Pawn (P or p), Rook (R or r), Knight (N or n),// Bishop (B or b), Queen (Q or q), King (K or k).// The dots represent empty places./// This is documentation for the Color enum data type./// This is the color of the pieces in a chess game.pubenumColor { White Black}/// This is documentation for the Piece enum./// It represents the different pieces in a chess game.pubenumPiece { Pawn Rook Knight Bishop Queen King}如上所示,在定义前使用三个斜杠(///)可为方法、数据类型或函数添加文档注释。使用两个斜杠(//)则表示单行注释。pub关键字表示这些定义对其他文件或模块是公开的。枚举类型(enum)定义了一种新的类型,其值只能是大括号内指定的选项。例如,Color的值只能是White或Black,Piece的值只能是Pawn、Rook、Knight、Bishop、Queen或King之一。
2、内置Trait的自动派生
在之前的枚举定义中,我们可以添加derive(Show, Eq),自动为这些枚举实现Show和Eq特性。这意味着我们可以直接比较和打印Color或Piece的实例。
pub enum Color {..} derive(Show, Eq)pub enum Piece {..} derive(Show, Eq)例如,我们可以编写一个函数来比较棋子:
pub enum Color { ..} derive(Show, Eq)pub enum Piece { ..} derive(Show, Eq)pub fn compare_pieces(piece: Piece) -> Unit { if piece == Pawn { println("The piece is a pawn") } else { println("The piece is a " + piece.to_string()) }}在这个示例中,我们可以直接使用 == 运算符比较Piece的实例,因为Piece实现了Eq特性。同时,我们可以使用to_string()方法打印Piece的实例,因为它实现了Show特性。
3、类型别名
在定义棋盘时,我们可以使用类型别名来提高代码的可读性和可维护性。例如,定义BoardPlace为Option[(Piece, Color)],表示棋盘上的每个位置要么为空,要么包含一个特定颜色的棋子。
/// This is the representation of a place on a chess board./// It can be empty (None) or contain a piece with a color: Some((piece, color)).pub typealias BoardPlace = Option[(Piece, Color)]通过这种定义方式,在代码中任何位置,我们都可以用BoardPlace代替对应的Option类型,反之亦然。这只是右侧类型定义的简化表达方式。另外,值得注意的是,Option数据类型内置于MoonBit语言的标准库中,与Rust和Scala类似。MoonBit还内置了Result数据类型,它与Scala中的Either类型类似,但更专注于错误处理。
4、模式匹配
模式匹配(Pattern Matching) 对熟悉Haskell、Scala或Rust的开发者而言,“模式匹配”是一个常见概念。在MoonBit中,可以通过如下方式定义一个使用模式匹配的函数:
fn draw(self: BoardPlace) -> String { match self { None => "." // empty place Some((piece, Color::White)) => pieceToString.get*or_default(piece, ".") Some((piece, Color::Black)) => pieceToString.get_or_default(piece, ".").to_lower() }}这里,pieceToString是一个映射(map):
let pieceToString: Map[Piece, String] = Map::of([ (Piece::Pawn, "P"), (Piece::Rook, "R"), (Piece::Knight, "N"), (Piece::Bishop, "B"), (Piece::Queen, "Q"), (Piece::King, "K")])上述函数的输入是BoardPlace类型,输出则是表示棋盘上该位置棋子的字符串。此外,你还可以使用特殊的通配符 *,来匹配所有未被前面的模式匹配到的其他情况。
需要注意的是,在MoonBit中,match 和 if 关键字都是表达式(expressions),而非语句(statements)。因此,它们会返回一个值。
与Scala类似,在一个由花括号 {} 围成的代码块中,最后一个表达式的值即为该代码块的返回值。这一点在函数中同样适用,例如:
pub fn abs(a: Int) -> Int { let absolute: Int = if a >= 0 { a } else { -a } return absolute}当省略掉return关键字时,也能达到完全相同的效果:
pub fn abs(a: Int) -> Int { let absolute: Int = if a >= 0 { a } else { -a } absolute}然而,在某些场景中,使用显式的return语句仍然是非常有用的,特别是当你希望提前返回(early return),跳过函数剩余逻辑处理特定情况时:
pub fn early_return(a: String) -> Bool { if a == "." { return false}// go on with the function logic:// at this point you know that a is NOT “.”// ...}5、结构体类型
结构体(struct)类型允许通过组合多个不同类型的字段来构造出新的数据类型。这种机制类似于其他编程语言中的类(class),特别是在结构体中加入方法定义以及信息隐藏(封装)时,更是如此。
例如,我们可以这样定义棋盘上的一行(Row):
/// This is a struct that represents a row in the boardpub struct Row { // Array type definition: priv cols: Array[BoardPlace] // information hiding: private fields} derive(Show, Eq)再定义整个棋盘(Board)的网格结构以及棋盘当前的状态(BoardState):
/// This is a struct that represents the board gridpub struct Board { priv grid: Array[Row]}/// This is a struct that represents the board statepub struct BoardState { priv board: Board priv turn: Turn}以上定义清晰地表达了棋盘元素及棋盘状态的结构。
当我们想在Row这个结构体的命名空间(namespace)下添加方法时,有两种方式:
方法一: 此方法定义了一个没有任何棋子的棋盘行。注意 Row:: 这个前缀,它明确表明这是针对类型Row定义的方法。
pub fn Row::empty_row() -> Row { { cols: Array::make(8, None) }}方式二: 如果方法需要访问结构体自身(self)的数据,定义方式则如下:
// fn <name>(self: <type>, <parameters>) -> <return type> { <body> }// And then you can call: <object>.<name>(<parameters>)pub fn get_turn(self: BoardState) -> Turn { self.turn}例如,当board_state是BoardState类型的实例时,我们就可以通过 board_state.get_turn() 来获取当前国际象棋游戏中的回合(Turn)信息。
6、运算符重载
可以通过重载“[]”运算符,以允许对棋盘行中的元素进行索引操作,如下面的代码片段所示。你只需为你的类型(在本例中为Row类型)重载**op_get()**方法即可:
// This special method name "op_get" is used to overload the [] operator.pub fn op_get(self:Row, index: Int) -> BoardPlace { self.cols[index]}To allow for indexed assignment operations, you can override the op_set() method:为了允许索引赋值操作,你还可以重载 op_set() 方法:
pub fn op_set(self: Row, index: Int, value: BoardPlace) -> Unit { self.cols[index] = value;}例如,现在你可以这样做:
pub fn check_first_element(row: Row) -> Unit { let element: BoardPlace = row[0] // Access the row with an index using “[]” operator if element is Some((Piece::Pawn, Color::White)) { println("First element is a white pawn") } ...}7、新类型定义
MoonBit允许你基于已有的类型定义一个新类型。例如,要定义Turn数据类型,我们可以这样做:
/// This is a new type that represents a turn in a chess game.pub type Turn Color现在,Turn就是一个新类型,类似于Scala语言中的opaque类型。要创建一个Turn类型的实例,你需要将值包装在类型名中:
pub fn BoardState::initialSetup!() -> BoardState { { board: Board::initialize!(), turn: Turn(Color::White) }}这种方式确保了颜色(Color)和回合(Turn)的值在编译时不会被混淆。
8、特性
下面是MoonBit中定义新特性的语法。由于它是“open”的,因此可以被扩展:
/// This trait defines a draw method that returns a string/// representation of the object./// It is used to draw the different objects in the chess game to a String./// (although it could be in another format or different resource, like a file or/// screen).pub(open) trait Drawable { draw(Self) -> String } pub(open) trait Paintable { paint(Self) -> Unit }我们定义了两个特性,每个特性中都有不同的(抽象)方法:draw() 和 paint()。这类似于 Java 中的接口或 Scala 中的 trait。
两个特性可以通过“+”运算符进行组合或继承:
// This is how you extend and combine traits in MoonBit language.pub trait DrawableAndPaintable : Drawable + Paintable {}特性中的方法通过以下方式进行实现:
/// Implement Drawable for BoardPlace traitpub impl Drawable for BoardPlace with draw(self: BoardPlace) -> String { ...}如你所见,我在 BoardPlace 类型上实现了 draw() 方法(以满足 Drawable 接口的要求)。如果我们同样为 BoardPlace 类型实现** paint() ** 方法,那么该数据类型也将满足** Paintable ** 和 DrawableAndPaintable 。
接下来,我们还可以为 Row 类型实现 draw() 方法:
/// Implement Drawable for Rowimpl Drawable for Row with draw(self: Row) -> String { ...}9、内置测试
通过定义一个辅助函数,我们可以根据字符串生成一行新的棋盘数据:
pub fn Row::new_row_from_string!(rowStr: String) -> Row { assert_eq!(rowStr.length(), 8) let cols = [] // for loops in MoonBit for i in 0..=7 { cols.push(new_place_from_char(rowStr[i])) } { cols: cols }}这是在 MoonBit 中定义 for 循环的方式,用于从 0 到 7(包含7)进行迭代。我将输入字符串中的每个棋子依次插入到 cols 数组中。assert_eq! 语句用于检查 rowStr 参数的长度是否为 8,以确保可以正确构造出一行。最后一行返回一个新的 Row 对象。
接下来,我们可以在代码的任何位置使用 test 关键字定义测试:
test "create a white row from string" { let my_row: Row = Row::new_row_from_string!("RNBQKBNR") assert*eq!(my_row[0], Some((Piece::Rook, Color::White))) assert_eq!(my_row[1], Some((Piece::Knight, Color::White))) assert_eq!(my_row[2], Some((Piece::Bishop, Color::White))) assert_eq!(my_row[3], Some((Piece::Queen, Color::White))) assert_eq!(my_row[4], Some((Piece::King, Color::White))) assert_eq!(my_row[5], Some((Piece::Bishop, Color::White))) assert_eq!(my_row[6], Some((Piece::Knight, Color::White))) assert_eq!(my_row[7], Some((Piece::Rook, Color::White)))}这种方式非常简洁,我们无需依赖其他测试框架,就可以直接在代码中嵌入测试块,用来验证某些性质是否一直成立,特别是在持续开发新功能或重构代码时非常有帮助。
10、函数式编程支持
让我们回顾上一节中定义的 new_row_from_string() 函数。我们原本使用** for** 循环逐个将棋子压入行数组中,但其实可以使用数组的 map 函数来生成这些元素:
pub fn Row::new_row_from_string!(rowStr: String) -> Row { assert_eq!(rowStr.length(), 8) { cols: rowStr.to_array().map(new_place_from_char) }}现在,它变成了一行搞定!
这个函数的逻辑是:将字符串转换为字符数组,然后逐个字符传入 new_place_from_char() 函数,用以生成 cols 数组。最后的表达式构造并返回一个包含 cols 的结构体实例。
另外,作为一个额外的特性,MoonBit 支持泛型数据类型,你可以用它来定义集合或参数化类型:
fn count[A](list : @immut/list.T[A]) -> UInt { match list { Nil => 0 Cons(*, rest) => count(rest) + 1 }}更多关于泛型和函数式编程的细节将在后续文章中介绍!
优势1、垃圾回收
MoonBit 是一种表达能力非常强的语言,在许多方面与 Rust 相似,但不采用 Rust 中“借用”和“所有权”的内存管理概念。虽然这些机制能带来内存安全,但在我看来它们太底层、使用起来也不够友好。而 MoonBit 使用的是垃圾回收机制来回收内存空间,这使得语言对开发者更友好,编码体验也更加简洁自然。
2、工具链
本次示例我只写了大约 400 行代码,但用来运行和测试程序的工具(如 moon 命令行工具)以及 VS Code 插件,给我的感觉是相当稳定、实用,能够很好地支持大型应用的开发。唯一的不足是调试器有时会显示局部变量的内部表示形式,而不是它们实际的值,这不太直观。
3、性能表现
虽然我只用 MoonBit 编程了几个小时,但它的编译和运行速度都非常快!编译器主要面向 WASM(WebAssembly)优化,但也支持编译为 JavaScript 和其他平台的代码。
你可以在 MoonBit 官方网站(https://www.MoonBitlang.com/)查看一些性能基准测试:

(此处原文附有链接和图表,建议前往官网获取最新数据)
令人惊讶的是,在一些基准测试中,MoonBit 的表现甚至超过了 Rust 和 Go。MoonBit 能够生成体积紧凑的二进制文件,这在 Web 环境中能显著提升加载速度和运行性能,使部署变得更容易、更快速、更具成本效益。
4、与 Scala 的对比
MoonBit 语言同样吸收了许多来自 Scala 的概念,比如“代码块返回最后一个表达式的值”。
MoonBit 的语言规模更小,也并未包含 Scala 中的所有特性。但考虑到 Scala 的学习曲线陡峭、精通难度较高,这反而可能是件好事——因为这意味着更容易让开发团队快速上手。虽然你不会拥有所有的函数式编程(FP)特性,但依然可以编写出非常不错的函数式代码,例如以下代码片段(摘自 MoonBit 官网):
fn main { resources .iter() .map*option(fn { (name, Text(str)) | (name, CSV(content=str)) => Some((name, str)) (*, Executable) => None }) .map(fn { (name, content) => { letname = name.pad*start(10, ' ') letcontent = content .pad_end(10, ' ') .replace_all(old="\n", new=" ") .substring(start=0, end=10) "\{name}: \{content} ..." } }) .intersperse("\n") .fold(init="Summary:\n", String::op_add) |> println}你可以使用 Lambda 表达式、特性(traits)、结构体(structs,代替类)以及高阶函数。此外,就像在 Rust 和 Scala 中一样,MoonBit 也内建了 Option 和 Result 数据类型。Scala 在表达能力和灵活性方面更强,但也更复杂。
Scala 还能调用所有 Java 的库——这些库经过多年发展,数量庞大且非常稳定;相比之下,MoonBit 当前可用的库数量不多,成熟度也相对较低(在官方网站上,大约有 250 个左右的库可供使用)。
Moon CLI 也作为包管理器使用,例如:moon add peter-jerry-ye/async。这条命令告诉项目添加一个名为 peter-jerry-ye/async 的依赖项。
5、社区
MoonBit 的社区规模尚不如 Rust 或 Scala,那意味着目前在网上找资料会比较困难,AI 编程助手(如 LLM 和 Copilot)对 MoonBit 的支持也还不够完善。
起初,我认为这个语言还非常不成熟,但当我在 https://mooncakes.io/ 上查看其可用库时,发现其实 MoonBit 已经涵盖了许多基础领域的库,例如 HTTP、异步编程、机器学习工具(如 torch)等。
此外,MoonBit 还内置了 Json 数据类型,这对于开发需要处理 HTTP JSON 服务的程序员来说非常实用:
fn main { let json_example : Json = { "array": ["a", "b"], "age": 22, "name": "John", "boolean": True } let greeting = match json_example { { "age": Number(age), "name": String(name) } => "Hello \{name}. You are \{age}" * => "not match" } greeting |> println}最后总结截至2025年3月,MoonBit 已经超越测试阶段。其编译器(包括 WebAssembly 后端)已于2024年12月开源,这标志着向稳定版本迈出了重要一步。MoonBit 团队正在稳步推进 1.0 正式版的发布,预计将包括对异步支持和嵌入式编程能力的集成。
凭借其现代化的语言特性、高性能以及生成的二进制文件体积小,MoonBit 在部署到云端时非常轻便且成本低。
尽管 MoonBit 的表达能力不如 Scala 丰富、简洁,因此暂时还不能完全取代 Scala,但它目前在很多方面可以与 Rust 相抗衡。这使得 MoonBit 在某些商业领域具备强大的成功潜力。