挑战 Rust 和 Scala,这门新语言震惊德国开发者


原标题:MoonBit 语言的十大特性(MoonBit Language in 10 Features)

原文链接:https://medium.com/@hivemind_tech/moonbit-language-in-10-features-4dc41a3a1d6c
作者:Ignacio丨德国科技公司 Hivemind 工程师

作为一名Scala开发者,我最近注意到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、类型别名

在定义棋盘时,我们可以使用类型别名来提高代码的可读性和可维护性。例如,定义BoardPlaceOption[(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 board
pub 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 grid
pub struct Board {
    priv grid: Array[Row]
}
/// This is a struct that represents the board state
pub 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_stateBoardState类型的实例时,我们就可以通过 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 trait
pub impl Drawable for BoardPlace with draw(self: BoardPlace) -> String {
    ...
}

如你所见,我在 BoardPlace 类型上实现了 draw() 方法(以满足 Drawable 接口的要求)。如果我们同样为 BoardPlace 类型实现** paint() ** 方法,那么该数据类型也将满足** Paintable ** 和 DrawableAndPaintable 。

接下来,我们还可以为 Row 类型实现 draw() 方法:

/// Implement Drawable for Row
impl 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 在某些商业领域具备强大的成功潜力。


MoonBit(https://www.moonbitlang.cn/)是国内首个工业级编程语言及其配套工具链,由粤港澳大湾区数字经济研究院(简称“IDEA 研究院”)基础软件中心打造的 AI 原生的编程语言以及开发者平台。通过创新框架在程序语言界形成后发优势,在编译速度、运行速度、体积大小上已成功领先传统语言。


相關推薦

2025-04-18

也可供选择。新的构建工具如Scala-CLI和Mill已经出现,而开发者工具如Scalafmt和Scalafix已经得到广泛应用。IDE仍然是一个痛点,但我们预计到2025年它们会有所改进。重用符号运算符的潮流已经逐渐式微。 Scala 一直处于语言前沿,

2022-11-14

;甚至相对年轻的 Swift 也已经面世八年了。 期望一种新语言在这些排名中表现出色是不合理的。它们本质上是递增的,要从长远使用和讨论方面看才更有利。但有趣的是,我们 RedMonk 并没有真正遇到许多新兴的编程语言,只

2023-01-21

JetBrains 的第六次年度开发者生态系统调查《The State of Developer Ecosystem 2022》结果已发布。超过 38,000 名开发者参与了去年的调查,报告基于其中 29,000 人的反馈,大约 91% 的受访者是男性;深入探讨了开发者生态系统的各种主题。

2022-01-17

供恢复选项。同时,该平台还建立了一个 100 万美元的“开发者防御基金”,以保护开发者免受“无理的 DMCA 第 1201 条的夺权索赔”。 而早在 GitHub 之前,Uberspace 在德国就已经收到了相关禁令。RIAA 抱怨称,虽然该工具本

2023-11-15

23 年薪资排行榜首的是 Solidity,平均薪资为 18.8 万美元。这门以太坊背后的前团队发明的语言最近受到了广泛关注。相关职位的求职者人数少,从而使得薪酬直线上升。Solidity 是一种高级面向对象编程语言。它用于编写当今大多

2023-06-30

证实了此说法。 他们表示,到目前为止,构建速度慢是开发者在使用 Rust 时遇到的第一大挑战,只有略多于 40% 的受访者认为速度可以接受。 3. unsafe 代码和互操作始终是最大的挑战 谷歌驳斥了此说法。 对于当前的谷歌开发

2022-10-05

va 规则文档中的错别字 java 在回归测试中添加使用新语言结构的 java 项目示例 scala 升级 scala-library 到 2.12.7/2.13.9 升级 scalameta 到4.6.0 …… CPD CLI CPD 现在支持在分析 Lua 代码时使用 --ignore-literal-sequences 参数

2023-03-06

。报告称,如果这种增长持续下去,其可能很快就会直接挑战 Java 和 Python 的地位。Go 是其中第三大最受欢迎的语言,其次是 C++、JavaScript、C#、C、Rust、TypeScript、R、Kotlin 和 Scala。 容器、Linux 和 Kubernetes 是热门话题。容器增长

2023-11-30

JetBrains 最新发布的 2023 年度开发者生态系统调查报告指出,在面向 Java 开发者的调查中,更多的开发人员选择在生产中使用 Java 17,而不是 Java 11。Docker 逐渐成为打包 Web 应用程序的首选,且 Spring 和 Spring Boot 的使用率遥遥

2022-10-09

域进行了应用。正如分析师 RedMonk 所说,Rust 是当下的“开发者宠儿”;凭借着内存安全特性,它也将是 C/C++ 的有力竞争者。 每种语言都有自己的 style guides,一些足够流行的语言可能还会有来自 major users 的多种 style guides

2022-09-30

二进制版本一起提供 微软首席工程师:Rust 将面临十大挑战  

2023-01-19

这项决定背后的原因和权衡。 Shopify CLI 是 Shopify 平台开发者的重要工具,能够用于构建主题、应用程序和 Hydrogen 开发,让开发者以最佳实践创建新项目并与平台整合,或是发布生产构件供店家使用。不过 Shopify CLI 开发团队发

2022-03-24

求已经达到了 2020 年的两倍以上。 该报告基于对公司与开发人员在其就业市场上的 366,000 多次互动数据的分析,以及 2020 年 1 月至 2021 年 12 月期间收集的 2000 份调查回复。旨在为工程师提供洞察力和资源,以促进他们的职业发

2022-10-31

大更新。本次发布包含了2个server版本,1个go-sdk版本以及新语言sdk的预告。 以下内容来自发布公告: Nacos 2.1.2 2.1.2 主要增强了控制台的UI效果,变更了控制台的样式,使得内容更加紧凑美观; 另外2.1.2对客户端大小进行了优