MoonBit:新手之旅#
这一文档针对不熟悉语言的新手用户所写,并不打算作为一个几分钟就能读完的小文章。本文希望为那些对 MoonBit 的编程思路 (更加现代化,函数式的) 不甚了解的用户提供一个简洁而不失易懂性的指南。
如果您想直接深入了解语言,请参阅总体介绍。
安装#
语言扩展
目前,MoonBit 的开发支持是通过 VS Code 扩展实现的。请前往VS Code Marketplace下载 MoonBit 语言支持。
工具链
(推荐) 如果您已安装了上面的扩展,运行操作菜单中的 “Install moonbit toolchain” 即可直接安装运行时并跳过这部分介绍:
我们还提供了一个安装脚本:Linux 和 macOS 用户可以通过以下方式安装:
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash
对于 Windows 用户,使用 Powershell:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser; irm https://cli.moonbitlang.com/install/powershell.ps1 | iex
这将自动安装 MoonBit 到 $HOME/.moon
并将其添加到您的 PATH
。
如果安装后遇到 moon
未找到的情况,请尝试重新启动终端或 VSCode 以使环境变量生效。
请注意,目前 MoonBit 还不适用于生产环境:它正在积极开发中。要更新 MoonBit,只需再次运行上面的命令即可。
运行 moon help
可以看到一堆子命令。但是现在我们只需要 build
run
和 new
这三个命令。
要创建一个项目(或模块,更正式地说),运行 moon new
。您将看到一个创建向导。
如果您选择创建一个 exec
模式项目,您将得到:
my-project
├── LICENSE
├── moon.mod.json
├── README.md
└── src
├── lib
│ ├── hello.mbt
│ ├── hello_test.mbt
│ └── moon.pkg.json
└── main
├── main.mbt
└── moon.pkg.json
其中包含一个 main
lib,其中包含一个 fn main
,作为程序的入口。尝试运行 moon run src/main
。
如果你选择创建一个 lib
模式项目,你将得到:
my-project
├── LICENSE
├── moon.mod.json
├── README.md
└── src
├── lib
│ ├── hello.mbt
│ ├── hello_test.mbt
│ └── moon.pkg.json
├── moon.pkg.json
└── top.mbt
在本教程中,我们将使用 lib
模式项目,并假设项目名称为 examine
。
示例:找到通过考试的人#
在这个例子中,我们将尝试找出,给定一些学生的分数,有多少人通过了考试?
为此,我们将从定义数据类型开始,确定我们的函数,并编写我们的测试。然后我们将实现我们的函数。
除非另有说明,以下内容将在文件 src/top.mbt
中定义。
数据类型#
MoonBit 中的基本数据类型包括以下内容:
Unit
Bool
Int
,UInt
,Int64
,UInt64
,Byte
, …Float
,Double
Char
,String
, …Array[T]
, …元组,和其他类型
要使用原始类型表示包含学生 ID 和分数的记录,我们可以使用一个包含学生 ID (类型为 String
)和分数(类型为 Double
)的 2-元组,如 (String, Double)
。然而,这并不是很直观,因为我们无法区分其他可能的数据类型,例如包含学生 ID 和学生身高的记录。
因此,我们选择使用 结构体 声明我们自己的数据类型:
struct Student {
id : String
score : Double
}
一个人可以通过或者不通过考试,因此判断结果可以使用 枚举类型 定义:
enum ExamResult {
Pass
Fail
}
函数#
函数 是一段代码,它接受一些输入并产生一个结果。
在我们的例子中,我们需要判断一个学生是否通过了考试:
fn is_qualified(student : Student, criteria: Double) -> ExamResult {
...
}
这个函数接受一个类型为 Student
的输入 student
,一个类型为 Double
的输入 criteria
(因为每门课程的标准可能不同,每个国家的标准可能不同)然后返回一个 ExamResult
。
使用 ...
语法可以让我们暂时不实现函数。
我们还需要找出有多少学生通过了考试:
fn count_qualified_students(
students : Array[Student],
is_qualified : (Student) -> ExamResult
) -> Int {
...
}
在 MoonBit 中,函数是一等公民,这意味着我们可以将一个函数绑定到一个变量,将一个函数作为参数传递或将一个函数作为结果接收。这个函数接受一个学生记录数组和另一个函数,判断学生是否通过了考试。
编写测试#
我们可以定义内联测试来定义函数的预期行为。这也有助于确保在重构程序时不会出现回归(破坏现有行为)。
test "is qualified" {
assert_eq!(is_qualified(Student::{ id : "0", score : 50.0 }, 60.0), Fail)
assert_eq!(is_qualified(Student::{ id : "1", score : 60.0 }, 60.0), Pass)
assert_eq!(is_qualified(Student::{ id : "2", score : 13.0 }, 7.0), Pass)
}
我们会收到报错信息,提醒我们 ExamResult
没有实现 Show
和 Eq
。
Show
和 Eq
是 traits。在 MoonBit 中,trait 定义了一个类型应该能够执行的一些常见操作。
例如,Eq
定义了应该有一种方法来比较两个相同类型的值,这个方法叫做 op_equal
:
trait Eq {
op_equal(Self, Self) -> Bool
}
Show
定义了应该有一种方法,要么将一个类型的值转换为 String
,要么使用 Logger
写入:
trait Show {
output(Self, &Logger) -> Unit
to_string(Self) -> String
}
assert_eq
使用它们来约束传递的参数,以便比较两个值并在它们不相等时打印它们:
fn assert_eq![A : Eq + Show](value : A, other : A) -> Unit {
...
}
我们需要为我们的 ExamResult
实现 Eq
和 Show
。有两种方法可以实现。
通过定义一个显式实现:
impl Eq for ExamResult with op_equal(self, other) { match (self, other) { (Pass, Pass) | (Fail, Fail) => true _ => false } }
在这里,我们使用 模式匹配 来检查
ExamResult
的情况。另一种方法是通过 派生 ,因为
Eq
和Show
是 内置 traits 并且ExamResult
的输出非常直接:enum ExamResult { Pass Fail } derive(Show)
现在我们已经实现了 traits,我们可以继续实现我们的测试:
test "count qualified students" {
let students = [
{ id: "0", score: 10.0 },
{ id: "1", score: 50.0 },
{ id: "2", score: 61.0 },
]
let criteria1 = fn(student) { is_qualified(student, 10) }
let criteria2 = fn(student) { is_qualified(student, 50) }
assert_eq!(count_qualified_students(students, criteria1), 3)
assert_eq!(count_qualified_students(students, criteria2), 2)
}
在这里,我们使用 lambda 表达式 来重用先前定义的 is_qualified
来创建不同的标准。
我们可以运行 moon test
来查看测试是否成功。
实现函数#
对于 is_qualified
函数,只需要简单的比较:
fn is_qualified(student : Student, criteria : Double) -> ExamResult {
if student.score >= criteria {
Pass
} else {
Fail
}
}
在 MoonBit 中,最后一个表达式的结果是函数的返回值,每个分支的结果是 if
表达式的值。
对于 count_qualified_students
函数,我们需要遍历数组,检查每个学生是否通过。
一个简单的版本是使用一个可变值和一个 for
循环:
fn count_qualified_students(
students : Array[Student],
is_qualified : (Student) -> ExamResult
) -> Int {
let mut count = 0
for i = 0; i < students.length(); i = i + 1 {
if is_qualified(students[i]) == Pass {
count += 1
}
}
count
}
然而,这既不高效(由于边界检查)也不直观,所以我们可以用 for .. in
循环 替换 for
loop:
fn count_qualified_students(
students : Array[Student],
is_qualified : (Student) -> ExamResult
) -> Int {
let mut count = 0
for student in students {
if is_qualified(student) == Pass { count += 1}
}
count
}
还有另一种方法是使用为 迭代器
fn count_qualified_students(
students : Array[Student],
is_qualified : (Student) -> ExamResult
) -> Int {
students.iter().filter(fn(student) { is_qualified(student) == Pass }).count()
}
现在之前定义的测试应该通过了。
公开库#
恭喜您完成了第一个 MoonBit 库!
您现在可以与其他开发人员分享它,这样他们就不需要重复您所做的工作。
但在此之前,您还有一些其他事情要做。
调整可见性#
为了看到其他人如何使用我们的程序,MoonBit 提供了一种称为 “黑盒测试” 的机制。
让我们将上面定义的 test
块移动到一个新文件 src/top_test.mbt
中。
糟糕!现在有错误抱怨:
is_qualified
和count_qualified_students
未绑定Fail
和Pass
未定义Student
不是一个记录类型,字段id
未找到,等等。
所有这些问题都来自于可见性问题。默认情况下,定义的函数对当前包(由当前文件夹绑定)之外的程序的其他部分不可见。默认情况下,类型被视为抽象类型,即我们只知道存在类型 Student
和类型 ExamResult
。通过使用黑盒测试,您可以确保您希望其他人拥有的一切确实被赋予了预期的可见性。
为了让其他人使用这些函数,我们需要在 fn
前添加 pub
使函数公开。
为了让其他人构造类型和读取内容,我们需要在 struct
和 enum
前添加 pub(all)
使类型公开。
我们还需要稍微修改 count qualified students
的测试,添加类型注释:
test "count qualified students" {
let students: Array[@examine.Student] = [
{ id: "0", score: 10.0 },
{ id: "1", score: 50.0 },
{ id: "2", score: 61.0 },
]
let criteria1 = fn(student) { @examine.is_qualified(student, 10) }
let criteria2 = fn(student) { @examine.is_qualified(student, 50) }
assert_eq!(@examine.count_qualified_students(students, criteria1), 3)
assert_eq!(@examine.count_qualified_students(students, criteria2), 2)
}
请注意,我们使用 @examine
访问类型和函数,这是您的包的名称。这是其他人使用您的包的方式,但您可以在黑盒测试中省略它们。
现在,编译应该可以正常工作,测试应该再次通过。
发布库#
现在您已经准备好了,您可以将这个项目发布到 mooncakes.io,MoonBit 的模块注册中心。您也可以在那里找到其他有趣的项目。
执行
moon login
并按照说明使用现有的 GitHub 账户创建您的账户。在
moon.mod.json
中修改项目名称为<您的 GitHub 账户名>/<项目名称>
。运行moon check
查看moon.pkg.json
中是否有其他受影响的地方。执行
moon publish
,您就完成了。您的项目将可供他人使用。
默认情况下,项目将在 Apache 2.0 下共享,这是一种宽松的许可证,允许每个人使用。您还可以通过更改 moon.mod.json
中的 license
字段和 LICENSE
的内容,使用其他许可证,例如 MulanPSL 2.0。
结束语#
到目前为止,我们已经了解了 MoonBit 的基本特性和一些不那么简单的特性,然而 MoonBit 是一个功能丰富的、多范式的编程语言。访问 语言导览 了解更多语法和基本类型的信息,以及其他文档,更好地掌握 MoonBit。