2024-05-01
Moonbit, 启动
差不多工作了 5 年,现在对 JS/TS 语言构造愈发感到不快了,所以最近都在学点其他的语言调剂一下,不然真的要废内功了。
近期看了下由张宏波团队开发的国产编程语言 MoonBit,一眼就爱上了,是 OCaml like 的语言,有股 rust 味,但是没有 rust 生命周期那些烧脑的特性,语言自带 GC。
本文介绍一下我感觉很好的地方 (rust with gc 笑)

# 非常好安装体验

查看官网 https://www.moonbitlang.cn/download/ 有详细说明,基本是傻瓜式操作,而且由于是国产的下载速度非常快,我估计不会有人卡在这一步的,在安装完之后就可以用
moon
命令来使用相关工具链了,比如这样新建项目进行开发:
00$ moon new myfirst  # 创建一个叫做 myfirst 的项目
01$ cd myfirst        # chdir 到项目里
02$ moon run main     # 编译运行

# 一等公民函数

在 moonbit 里函数是一等公民,使用词法作用域,因此会有跟 js 一样的闭包效果:
00// 写在最外面的叫做「顶层函数」
01fn counter() -> () -> Int {
02  let mut i = 0 // 可变值需要带上 mut 标记
03
04  // 嵌套在内的叫做「局部函数」
05  // 局部函数的签名可以比较简略,编译器会自动推导
06  fn inc() {
07    // 函数走的是跟 js 一样的词法作用域
08    let r = i
09    i = i + 1
10    return r
11  }
12
13  return inc
14}
15
16fn main {
17  // 因此也会有一模一样的闭包效果
18  let count = counter()
19  println(count()) // 0
20  println(count()) // 1
21}

# 带标签的函数参数

支持标签参数、可选参数等高级特性:
00// 此处的内置类型 Ref 可以理解为 ts: type Ref<T> = { val: T }
01// 1. 用 ~counter 的方式来给参数加标签
02// 2. 用 = 来给参数带一个默认值使其变成可选参数
03// 3. 默认值每次执行都会给一个「新的」
04fn incr(~counter : Ref[Int] = { val: 0 }) -> Ref[Int] {
05  counter.val = counter.val + 1
06  counter
07}
08
09fn main {
10  println(incr()) // 1
11  println(incr()) // 依然是 1,因为重新求值了默认表达式,产生了一个新的 Ref
12  let counter : Ref[Int] = { val: 0 }
13  println(incr(~counter)) // 1
14  println(incr(~counter)) // 2,因为两次调用使用了同一个 Ref
15}
参数默认值每次执行都会执行新的,不会复用,如果想一直复用同一个默认值,那么可以将其提升到全局
00let default_counter : Ref[Int] = { val: 0 }
01
02fn incr(~conuter : Ref[Int] = default_counter) -> Int {
03  counter.val = counter.val + 1
04  counter.val
05}
06
07fn main {
08  println(incr()) // 1
09  println(incr()) // 2
10}

# 函数式循环

看了真过瘾,专门给 ADT 设计的 reduce 循环:
00fn sum(xs: List[Int]) -> Int {
01  loop xs, 0 {
02    Nil, acc => break acc // break 可以省略
03    Cons(x, rest), acc => continue rest, x + acc
04  }
05}
06
07fn init {
08  println(sum(Cons(1, Cons(2, Cons(3, Nil))))) // 6
09}

# 多行字符串

字符串底下用 UTF-16 进行编码,除了常用的字符串优秀实践外,还提供了多行字符串(js 的模板字符串在多行的情况下会把缩进也一起编进去,实际使用很蛋疼)
00fn main {
01  let str = 
02    #| Hello
03    #| World
04  println(str)
05}

# enum adt

爷最爱的 adt
00enum DragState {
01  Invalid
02  Active(Int, Int)
03}
04
05fn printDragState(s: DragState) -> Unit {
06  match s {
07    Invalid => println("未激活拖拽")
08    Active(dx, dy) => {
09      println("拖拽中 ... dx=(dx) dy=(dy)")
10    }
11  }
12}
13
14fn init {
15  printDragState(Invalid)      // 未激活拖拽
16  printDragState(Active(1, 2)) // 拖拽中 ... dx=1 dy=2
17}

# 没有空值

由于提供了基于 ADT 构造的 Option 和 Result, moonbit 没有额外提供 null 这些空值了,配合
?.
问号操作符能写出更健壮的代码:
00fn may_fail() -> Option[Int] { ... }
01
02fn f() -> Option[Int] {
03  let x = may_fail()?
04  let y = may_fail()?.lsr(1) + 1
05  if y == 0 { return None }
06  Some(x / y)
07}
08
09fn may_error() -> Result[Int, String] { ... }
10
11fn g() -> Result[Int, String] {
12  let x = may_error()?
13  let y = may_error()? * 2
14  if y == 0 { return Err("divide by zero") }
15  Ok(x / y)
16}

# moonckes 包管理

moonbit 提供了 mooncakes.io 作为包管理平台,跟语言深度结合,比 npm 高到不知道哪里去, 下面是我发的一个测试包,自带文档工具

# EOF

这门语言是新开发的,目前仍在快速迭代中,从我去年第一次关注以来已经多了非常多功能了,前两天还发布了新的 js 后端 —— 也就是可以编译成 js 使用,试了下非常好,可以充分享受写高级语言的乐趣。