挑戰Rust和Scala,這門新語言震驚德國開發者!

 原標題:MoonBit 語言的十大特性(MoonBit Language in 10 Features)
OSCHINA
↑點選藍字 關注我們
原標題: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的例項。
pubenumColor

 {

..

derive

(Show, 

Eq

)

pubenumPiece

 {

..

derive

(Show, 

Eq

)

例如,我們可以編寫一個函式來比較棋子:
pubenumColor

 {

    ..

derive

(Show, 

Eq

)

pubenumPiece

 {

    ..

derive

(Show, 

Eq

)

pubfncompare_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中,可以透過如下方式定義一個使用模式匹配的函式:
fndraw

(

self

: BoardPlace) 

->String

 {

matchself

 {

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):
letpieceToString

: 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類似,在一個由花括號 {} 圍成的程式碼塊中,最後一個表示式的值即為該程式碼塊的返回值。這一點在函式中同樣適用,例如:
pubfnabs

(a: Int) 

->

 Int {

letabsolute

: Int = 

if

 a >= 

0

 { a } 

else

 { -a }

return

 absolute

}

當省略掉return關鍵字時,也能達到完全相同的效果:
pubfnabs

(a: Int) 

->

 Int {

letabsolute

: Int = 

if

 a >= 

0

 { a } 

else

 { -a }

    absolute

}

然而,在某些場景中,使用顯式的return語句仍然是非常有用的,特別是當你希望提前返回(early return),跳過函式剩餘邏輯處理特定情況時:
pubfnearly_return

(a: 

String

->

 Bool {

if

 a == 

"."

 {

returnfalse

}

// 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
pubstructRow

 {

// 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
pubstructBoard

 {

priv

 grid: Array[Row]

}

/// This is a struct that represents the board state
pubstructBoardState

 {

priv

 board: Board

priv

 turn: Turn

}

以上定義清晰地表達了棋盤元素及棋盤狀態的結構。
當我們想在Row這個結構體的名稱空間(namespace)下新增方法時,有兩種方式:
方法一: 此方法定義了一個沒有任何棋子的棋盤行。注意 Row:: 這個字首,它明確表明這是針對型別Row定義的方法。
pubfnRow

::

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>)
pubfnget_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.
pubfnop_get

(

self

:Row, index: Int) 

->

 BoardPlace {

self

.cols[index]

}To allow 

forindexed

 assignment operations, you can 

override

 the 

op_set

() method:

為了允許索引賦值操作,你還可以過載 op_set() 方法:
pubfnop_set

(

self

: Row, index: Int, value: BoardPlace) 

->

 Unit {

self

.cols[index] = value;

}

例如,現在你可以這樣做:
pubfncheck_first_element

(row: Row) 

->

 Unit {

letelement

: 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.
pubtypeTurn Color
現在,Turn就是一個新型別,類似於Scala語言中的opaque型別。要建立一個Turn型別的例項,你需要將值包裝在型別名中:
pubfnBoardState

::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) 

traitDrawable

 {

draw

(

Self

->String

    }

pub

(open) 

traitPaintable

 {

paint

(

Self

->

 Unit

    }

我們定義了兩個特性,每個特性中都有不同的(抽象)方法:draw() 和 paint()。這類似於 Java 中的介面或 Scala 中的 trait。
兩個特性可以透過“+”運算子進行組合或繼承:
// This is how you extend and combine traits in MoonBit language.
pubtraitDrawableAndPaintable : Drawable + Paintable {}
特性中的方法透過以下方式進行實現:
/// Implement Drawable for BoardPlace trait
pubimplDrawableforBoardPlace

 with 

draw

(

self

: BoardPlace) 

->String

 {

    ...

}

如你所見,我在 BoardPlace 型別上實現了 draw() 方法(以滿足 Drawable 介面的要求)。如果我們同樣為 BoardPlace 型別實現** paint() ** 方法,那麼該資料型別也將滿足** Paintable ** 和 DrawableAndPaintable 。
接下來,我們還可以為 Row 型別實現 draw() 方法:
/// Implement Drawable for Row
implDrawableforRow

 with 

draw

(

self

: Row) 

->String

 {

    ...

}

9、內建測試

透過定義一個輔助函式,我們可以根據字串生成一行新的棋盤資料:
pubfnRow

::new_row_from_string!(rowStr: 

String

->

 Row {

assert_eq!

(rowStr.

length

(), 

8

)

letcols

 = []

// for loops in MoonBit
foriin0

..=

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"

 {

letmy_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 函式來生成這些元素:
pubfnRow

::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 支援泛型資料型別,你可以用它來定義集合或引數化型別:
fncount

[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 官網):
fnmain

 {

    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 服務的程式設計師來說非常實用:
fnmain

 {

letjson_example

 : Json = {

"array"

: [

"a"

"b"

],

"age"

22

,

"name"

"John"

,

"boolean"

: True

    }

letgreeting

 = 

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 原生的程式語言以及開發者平臺。透過創新框架在程式語言界形成後發優勢,在編譯速度、執行速度、體積大小上已成功領先傳統語言。
推薦閱讀:

相關文章