跳到主要内容

2024-10-21

· 阅读需 6 分钟

MoonBit更新

  • MoonBit支持native后端

  • Wasm-gc 后端支持 Js-string-builtins proposal

当通过编译选项 -use-js-builtin-string 开启使用 Js-string-builtins 之后,Moonbit 面向 wasm-gc 后端时,会使用 JavaScript 中的字符串类型表示 MoonBit 中的字符串,这时生成的 wasm 可执行文件中将需要从 JavaScript 宿主中导入字符串类型相关的函数,这个过程可以通过在 JS 的胶水代码中,使用如下选项来实现:

// glue.js
// read wasm file
let bytes = read_file_to_bytes(module_name);
// compile the wasm module with js-string-builtin on
let module = new WebAssembly.Module(bytes, { builtins: ['js-string'], importedStringConstants: "moonbit:constant_strings" });
// instantiate the wasm module
let instance = new WebAssembly.Instance(module, spectest);
  • 整数字面量重载支持表示Byte类型
let b : Byte = 65
println(b) // b'\x41'
  • 多行字符串插值和转义支持

考虑到多行字符串有时用于保存raw string,即字符串内可能包含与转义序列冲突的字符序列。 MoonBit拓展了原先的多行字符串插值语法,用户可以通过开头的标记单独控制每行是否启用插值和转义序列:$|表示启用插值和转义,#|表示raw string。

let a = "string"
let b = 20
let c =
#| This is a multiline string
$| \ta is \{a},
$| \tb is \{b}
#| raw string \{not a interpolation}
println(c)

输出:

 This is a multiline string
a is string,
b is 20
raw string \{not a interpolation}
  • 带标签参数的语法调整

移除函数调用中f(~label=value)和模式匹配中Constr(~label=pattern)的语法,仅保留省略符号的形式:f(label=value)Constr(label=pattern)f(~value)Constr(~name)不受影响。

IDE 更新

  • 修复了字符串插值的高亮

标准库更新

  • Builtin包引入StringBuilder

StringBuilder针对不同的后端的字符串拼接操作进行了特化,例如JS后端在使用StringBuilder后相比原先的Buffer实现有大约五倍的速度提升。原先Builtin包的Buffer已经弃用,相关API移入moonbitlang/core/buffer包,后续会对BytesBuffer的API进行相关的调整。

  • 位运算函数调整

弃用了标准库中各个类型的lsr, asr, lsl, shr, shl等左移和右移位运算操作函数,只保留op_shlop_shr。目前lxor, lor, land, op_shr, op_shl都有对应的中缀运算符,我们推荐使用中缀表达式的风格。

  • 破坏性更新

immut/ListLast 函数现在返回 Option[T]

构建系统更新

  • 初步适配 native 后端

    • run | test | build | check 支持 --target native
    • native 后端的 moon test 在 debug 模式下(默认)用 tcc 编译;release 模式下用 cc 编译(unix),windows 暂未支持
    • 暂未支持 panic test
  • 支持 @json.inspect,被检查的对象需要实现 ToJson

    使用样例:

enum Color {
Red
} derive(ToJson)

struct Point {
x : Int
y : Int
color : Color
} derive(ToJson)

test {
@json.inspect!({ x: 0, y: 0, color: Color::Red })
}

执行 moon test -u 后,测试块被自动更新成:

test {
@json.inspect!({ x: 0, y: 0, color: Color::Red }, content={"x":0,"y":0,"color":{"$tag":"Red"}})
}

inspect 相比,@json.inspect 的自动更新结果可以使用代码格式化工具:

test {
@json.inspect!(
{ x: 0, y: 0, color: Color::Red },
content={ "x": 0, "y": 0, "color": { "$tag": "Red" } },
)
}

此外 moon test 会自动对 @json.inspect 中的JSON进行结构化对比,例如,对于如下代码

enum Color {
Red
Green
} derive(ToJson)

struct Point {
x : Int
y : Int
z : Int
color : Color
} derive(ToJson)

test {
@json.inspect!(
{ x: 0, y: 0, z: 0, color: Color::Green },
content={ "x": 0, "y": 0, "color": { "$tag": "Red" } },
)
}

moon test 的输出的 diff 结果类似:

Diff:
{
+ z: 0
color: {
- $tag: "Red"
+ $tag: "Green"
}
}
  • moon.mod.json 中支持 includeexclude 字段。includeexclude 是个字符串数组,字符串的格式与 .gitignore 中每行的格式相同。具体的规则如下:

    • 如果 includeexclude 字段都不存在,只考虑 .gitignore 文件
    • 如果 exclude 字段存在,同时考虑 exclude 中的路径与 .gitignore 文件
    • 如果 include 字段存在,那么 exclude.gitignore 都失效,只有在 include 中的文件才会被打包
    • moon.mod.json 忽略上述规则,无论如何都会被打包
    • /target/.mooncakes 忽略上述规则,无论如何都不会被打包
  • 添加 moon package 命令,用于只打包而不上传

    • moon package --list 用于列出包中的所有文件
  • 支持 moon publish --dry-run,服务端会进行校验,但是不会更新索引数据

2024-10-08

· 阅读需 3 分钟

IDE更新

  • AI Codelens支持 /generate/fix 命令

/generate 命令能够提供一个通用的用以生成代码的聊天界面。

generate.gif

/fix 命令能够读取当前函数的错误信息给出修复建议。

fix.gif

MoonBit更新

  • 调整中缀表达式和ifmatchloopwhilefortry表达式的优先级, 后者这些控制流表达式不再能够直接出现在要求是中缀表达式的位置,嵌套使用时需要增加一层括号。

例如ifmatch的语法是:

if <infix-expr> { <expression> } [else { <expression> }]
match <infix-expr> { <match-cases> }

因为ifmatchloopwhilefortry 都不再属于infix-expr, 因此if if expr {} {}这种代码不是合法的:

//invalid
if if cond {a} else {b} {v} else {d}
match match expr { ... } { ... }
let a = expr1 + expr2 + if a {b} else {c} + expr3
//valid
if (if cond {a} else {b}) {v} else {d}
match (match expr { ... }) { ... }
let a = expr1 + expr2 + (if a {b} else {c}) + expr3
  • js后端

    • 现在Array会编译到 js 的原生数组,和 js 交互更加方便
  • 标准库API

    • String包增加concat, from_array函数,弃用Array::join
    • immut/list包增加rev_concat()
    • Buffer类型增加lengthis_empty 函数
    • 改进了Option类型的to_json函数
  • 实验库API

    • x/fs 包支持 wasm, wasm-gc, js 后端,包含以下 api
      • write_string_to_file, write_bytes_to_file
      • read_file_to_string, read_file_to_bytes
      • path_exists
      • read_dir
      • create_dir
      • is_dir, is_file
      • remove_dir, remove_file

构建系统更新

  • moon test -p 支持模糊匹配功能,例如 moon test -p moonbitlang/core/builtin 可简写为 moon test -p mcb | moon test -p builtin 等。

  • moon.pkg.json 中的 source 字段为空字符串""时,等价于 ".",表示当前目录。

moondoc更新

文档生成器支持package层级的README文件,所有moon.pkg.json 同级的README.md会被一同显示在这个包的文档页面中。

weekly 2024-09-18

· 阅读需 8 分钟

MoonBit更新

  • type 支持将字段访问传递到内部的类型

    struct UnderlyingType {
    field1 : Int
    field2 : Bool
    }

    type Newtype UnderlyingType

    fn main {
    let newtype = Newtype({ field1: 100, field2: true })
    println(newtype.field1) // 100
    println(newtype.field2) // true
    }

    原先要操作newtype内的UnderlyingType的字段field1,必须以newtype._.field1形式访问。简化后,对Newtype的字段访问将自动传递到UnderlyingType:可以直接书写newtype.field1访问UnderlyingType内的field1。

  • 支持自定义类型通过 derive 实现 ToJsonFromJson trait

    derive(ToJson) 会自动为类型生成相关实现。输出的json格式符合自动生成的FromJson的要求。

    struct Record {
    field1 : Int
    field2 : Enum
    } derive(Show, ToJson, FromJson)

    enum Enum {
    Constr1(Int, Bool?)
    Constr2
    } derive(Show, ToJson, FromJson)

    fn main {
    let record = { field1: 20, field2: Constr1(5, Some(true)) }
    println(record.to_json().stringify())
    // output: {"field1":20,"field2":{"$tag":"Constr1","0":5,"1":true}}
    let json = record.to_json()
    try {
    let record : Record = @json.from_json!(json)
    println(record)
    // output: {field1: 20, field2: Constr1(5, Some(true))}
    } catch {
    @json.JsonDecodeError(err) => println(err)
    }
    }
  • 卫语句(guard statement)

    卫语句有guardguard let两种形式,用于保证不变量和消除模式匹配带来的缩进。

    fn init {
    guard invariant else { otherwise }
    continue_part
    }

    guard中的invariant是类型为Bool的表达式。当invariant为真时,对continue_part求值并返回结果;当invariant为假时,对otherwise求值并返回求值结果,跳过余下的continue_partelse { otherwise }部分是可选的,省略时,当invariant为假,程序终止。

    fn init {
    guard let ok_pattern = expr1 else {
    fail_pattern1 => expr2
    fail_pattern2 => expr3
    }
    continue_part
    }

    guard let 与 guard 类似,支持额外的模式匹配。当expr1可以被ok_pattern匹配时,执行continue_part;否则尝试匹配 else 内的分支。else 部分省略或者没有匹配的分支时程序终止。ok_pattern可以引入新的绑定,它的作用域是整个continue_part。一个例子:

    fn f(map : Map[String,Int]) -> Int!Error {
    guard let Some(x) = map["key1"] else {
    None => fail!("key1 not found")
    }
    x + 1
    }
  • moonfmt调整

    对于不在语句的上下文中使用的ifmatchloopwhilefortry 表达式,格式化后会自动加上括号。 我们将在下一周调整ifmatchloopwhilefortry和中缀表达式的优先级,这是一个破坏性的变更。调整后,前者这些表达式不再能够直接出现在语法上要求是中缀表达式的位置。例如,下面的代码未来都是不合法的:

    if if cond {a} else {b} {v} else {d}
    match match expr { ... } { ... }
    let a = expr1 + expr2 + if a {b} else {c} + expr3
    guard if a {b} else {c} else { d }

    调整后,原先的代码需要额外的括号:

    if (if cond {a} else {b}) {v} else {d}
    match (match expr { ... }) { ... }
    let a = expr1 + expr2 + (if a {b} else {c}) + expr3
    guard (if a {b} else {c}) else { d }

    我们更建议使用let x = yifmatch等表达式的中间结果引入新的绑定,来改善代码的可读性,这不会引入额外的开销。例如:

    // 不建议的用法
    match (match expr { ... }) + (if a {b} else {c}) + expr { ... }
    // 建议的用法
    let r1 = match expr { ... }
    let r2 = if a {b} else {c}
    match r1 + r2 + expr {
    ...
    }

    除此之外,对于 .land(), lor(), shl(), op_shr 等函数,格式化后会自动变为中缀运算符 &, |, <<, >>

IDE更新

  • 支持项目全局的符号搜索 signal.png
  • 修复重命名时会把包名覆盖的问题
  • 优化插件自动执行 moon check 的逻辑
  • 增加关键字和 Bool 字面量的补全
  • 适配构建系统的条件编译,同时兼容原来的文件名后缀区分后端的方式(x.wasm.mbt, x.js.mbt)

构建系统更新

  • 支持构建图可视化,在 moon check|build|test 后传递 --build-graph,完成编译后会在对应的构建目录中生成此次构建图的 .dot 文件

  • moon.pkg.json 添加 targets 字段,用于编写条件编译表达式,条件编译的粒度为文件。条件编译表达式中支持三种逻辑操作符 andornot,其中 or 可以省略不写,例如 ["or", "wasm", "wasm-gc"]可简写为 ["wasm", "wasm-gc"]。条件表达式中具体的条件分为后端和优化等级。其中后端包括:"wasm", "wasm-gc""js"。优化等级包括: "debug""release"。条件表达式支持嵌套。此外,如果一个文件未出现在 "targets" 中,默认在所有条件下都会编译。

    写法样例

    {
    "targets": {
    "only_js.mbt": ["js"],
    "not_js.mbt": ["not", "js"],
    "only_debug.mbt": ["and", "debug"],
    "js_and_release.mbt": ["and", "js", "release"],
    "js_only_test.mbt": ["js"],
    "complex.mbt": ["or", ["and", "wasm", "release"], ["and", "js", "debug"]]
    }
    }
  • moon.pkg.json 添加 pre-build 字段用于配置构建的前序命令,其描述的命令会在执行moon check|build|test之前执行。其中 pre-build 是一个数组,数组中的每个元素是一个对象,对象中包含 input output command 三个字段,inputoutput 可以是字符串或者字符串数组,command 是字符串,command 中可以使用任意命令行命令,以及 $input $output 变量,分别代表输入文件、输出文件,如果是数组默认使用空格分割。目前内置了一个特殊命令 :embed,用于将文件转换为MoonBit源码,-text 参数用于嵌入文本文件,-binary 用于嵌入二进制文件,-text 为默认值,可省略不写。-name 用于指定生成的变量名,默认值为 resource。命令的执行目录为当前 moon.pkg.json 所在目录。

写法样例:

moon.pkg.json

{
"pre-build": [
{
"input": "a.txt",
"output": "a.mbt",
"command": ":embed -i $input -o $output"
}
]
}

如果 a.txt 的内容为

hello,
world

执行 moon build 后,在此 moon.pkg.json所在目录下生成如下 a.mbt 文件

let resource : String =
#|hello,
#|world
#|
  • moon test --target all 添加[wasm] 等后端后缀,显示效果如下

    $ moon test --target all
    Total tests: 0, passed: 0, failed: 0. [wasm]
    Total tests: 0, passed: 0, failed: 0. [wasm-gc]
    Total tests: 0, passed: 0, failed: 0. [js]

weekly 2024-09-03

· 阅读需 7 分钟

MoonBit 更新

  • Breaking change: string interpolation的语法从\()改为\{},在\{}中支持了更多类型的表达式,例如:
fn add(i: Int) -> Int {
i + 1
}

fn main {
println("\{add(2)}") // cannot invoke function in \() before
}
  • 支持了一种新的 optional argument,可以由编译器自动插入 Some 构造器。类型为 Option 的可选参数是一种很常见的场景,默认的参数为 None,显式传递的参数使用 Some 构造器创建,例如:
fn image(~width: Option? = None, ~height: Option? = None, ~src: String) -> Unit

之前,在 MoonBit 中,使用这种默认参数时,需要调用者手动插入大量 Some 构造器,很不方便:

image(width=Some(300), height=Some(300), src="img.png")

为此,MoonBit 引入了一种新的 optional argument 的语法 ~label? : T(这一语法和 map pattern 中的 ? 有类似的含义):

fn image(~width?: Int, ~height?: Int, ~src: String) -> Unit

此时widthheight 是optional argument,默认值为None,它们在函数体内的类型为Int?。当调用 image 时,如果需要给 widthheight 提供值,调用者无需手动插入 Some,编译器会自动插入:

image(width=300, height=300, src="img.png")

如果有直接传递一个类型为Int?的值给 image 的需求,则可以使用下面的语法:

fn image_with_fixed_height(~width? : Int, ~src : String) -> Unit {
// `~width?` 是 `width?=width` 的简写
image(~width?, height=300, src="img.png")
}
  • 新增 range 操作符的支持。有两个 range 操作符,..< 不包含其上界,..= 包含其上界。Range 操作符可以用于简化 for 循环:
fn main {
for i in 1..<10 {
println(i)
}
}

目前 range 操作符只支持内建的 Int/UInt/Int64/UInt64 类型,且只能在 for .. in 循环中使用。未来可能会放宽这些限制

  • 新增<expr>._作为访问newtype的语法,之前<expr>.0的语法将在未来被弃用,目前旧语法会触发警告。这一修改的目的是,在使用 newtype 包裹一个 struct 或 tuple 类型时,可以把 newtype 上对字段的访问自动转发到被包裹的类型上,简化 newtype 的使用

  • 实现 trait 时,新增实现的一致性检查,所有 impl 必须有相同的签名:

trait MyTrait {
f1(Self) -> Unit
f2(Self) -> Unit
}

// 这里的两个 `impl` 的签名不一致,`f2` 的实现更一般,
// 但由于 `impl` 只能用来实现指定的 trait,`f2` 的一般性是没有意义的。
// 同一个 trait + 同一个而理性的所有 `impl` 的签名应当保持一致
impl[X : Show] MyTrait for Array[X] with f1(self) { .. }
impl[X] MyTrait for Array[X] with f2(self) { ... }

标准库更新

  • 废弃原有的名字为xx_exn的函数,重命名为unsafe_xx。例如:unsafe_popunsafe_nthunsafe_peek

  • Breaking change: 将浮点数转为字符串的操作修改为符合 ECMAScript 标准。

  • op_as_view的类型从

fn op_as_view[T](self : Array[T], ~start : Int, ~end : Int) -> ArrayView[T]

改成了

fn op_as_view[T](self : Array[T], ~start : Int, ~end? : Int) -> ArrayView[T]

这样 Iter 也可以实现 op_as_view 方法,进而可以使用切片语法。例如:

fn main {
let it: Iter[Int] = [1, 2, 3, 4].iter()[1:2] // slice syntax on iter

for x in it {
println(x) // 2
}
}

得益于新的 optional argument 语法 ~end? : Int,这一改动是完全向后兼容的:过去的所有调用 op_as_view 的方式依然可用且语义不变。

  • Int::to_uintUInt::to_int重命名为Int::reinterpret_as_uintUInt::reinterpret_as_int

  • 删除BigInt::lsr(因为名称与实际操作不符合),并对BigInt进行了Bug修复与性能提升

工具链更新

  • Breaking change: moon {build,check,test,run}文字版诊断信息输出(如打印的 error 和 warning 详情)从 stdout 移动到了 stderr,以保证 moon {test,run} 时 stdout 的输出不被多余信息污染。如果你的工具依赖 stdout 的文本格式诊断信息输出,请相对应地更新你的代码。

    JSON 模式的输出不受影响。

  • MoonBit AI 支持批量生成测试和文档。

ai-package

  • 支持快照测试,用法与 inspect! 类似,只不过会将结果写到文件,而不是更新当前文件。例如,当执行 moon test -u 时,以下测试块会在当前包的目录下的 __snapshot__ 文件夹中生成 001.txt 文件,文件内容为 ...\n
 test (it : @test.T) {
it.write(".")
it.writeln("..")
it.snapshot!(filename="001.txt")
}

注意,快照测试会忽略 LF 和 CRLF 的差异。

  • moon build 之前只支持构建moon.pkg.json中包含 is-maintrue 或者 linktrue|object 的包的项目。现在放宽了这个限制,即使没有这两个字段,也会进行构建,只不过最终没有链接成 wasm/js 的 步骤,每个包只会生成一个 core 文件。

  • moon fmt 支持增量格式化。第一次执行 moon fmt 时会格式化所有 .mbt 文件,后续只会格式化有变化的文件。

IDE更新

  • 支持了项目级别的go to references,例如在core里对inspect使用go to references:

reference

可以看到core中所有调用inspect的地方都被找到了

  • test block 增加快捷调试功能,现在可以方便的通过 test block 的 codelens 进入调试

codelens

weekly 2024-08-19

· 阅读需 2 分钟

MoonBit 更新

  • 8月18日,MoonBit AI 云原生开发平台 Pre-beta 版本正式发布!MoonBit beta 预览版比大部分主流语言更早推出现代化泛型、精准错误处理和高效迭代器等重要特性,在云计算、边缘计算、人工智能和教育等领域快速实现落地应用。欢迎查看最新博客,了解关于更多 pre-beta 版本的技术突破和创新应用

  • 改进错误类型打印,可以让实现了 Show 的错误类型在被用作 Error 类型的时候也可以打印出更细致的信息,比如:

type! MyErr String derive(Show)
fn f() -> Unit! { raise MyErr("err") }
fn main {
println(f?()) // 输出 Err(MyErr("err"))
}
  • test block 新增参数支持,参数的类型必须是 @test.T
test "hello" (it: @test.T) {
inspect!(it.name, content="hello")
}

构建系统更新

  • 修复当设置了 moon.mod.json 中的 source 字段时,moon info 写 mbti 文件路径不正确的问题

标准库更新

  • arrayfixedarray 增加 last 函数

  • test 包内常见的函数移动到了 builtin,旧的函数被弃用

  • iter 增加 lastheadintersperse 方法

IDE 更新

  • 支持 vscode test explorer

test explorer

  • 在线 IDE 支持多包编辑。在 try.moonbitlang.cn 上可以像本地一样开发有包之间依赖的MoonBit模块,比如图中main包依赖了lib包。

IDE

其他更新

  • 文档地址迁移,文档的网站迁移到 https://docs.moonbitlang.cn

  • 随着 MoonBit 进入 beta 预览版,语言特性趋于稳定,今后更新公告将调整为两周一次

weekly 2024-08-12

· 阅读需 7 分钟

MoonBit更新

  • 添加了基于 IterIter2 类型的 for .. in 循环支持:
fn main {
for x in [ 1, 2, 3 ] {
println(x)
}
for k, v in { "x": 1, "y": 2 } {
println("\{k} => \{v}")
}
}

forin 之间可以使用 1~2 个变量来绑定 Iter 中的元素。有一个变量的 for x in expr1 循环会遍历 expr1.iter() : Iter[_],有两个变量的 for x, y in expr2 循环会遍历 expr2.iter2() : Iter2[_, _] 中的元素。可以使用下划线代替变量来忽略元素,但不能在 forin 之间使用模式匹配。

for .. in 循环的循环体中,可以使用 return/break/raise 等控制流:

test "for/in" {
// 数组的 `iter2` 方法会遍历 index + 元素
for i, x in [ 1, 2, 3 ] {
assert_eq!(i + 1, x)
}
}
  • 引入新的字符串插值语法:\{},旧的语法\()已弃用,允许更复杂的表达式直接嵌入字符串。 未来会放松字符串插值内的语法限制,例如支持\{1 + 2}\{x.f(y)}
"hello, \(name)!" // warning: deprecated
"hello, \{name}!" // new syntax
  • 数值处理拓展:增加了新的内建类型BigInt,用于处理超出普通整数范围的大数值。
// BigInt 字面量以 N 结尾
let num = 100000000N

// 与 Int 字面量类似,数字间允许下划线。同样支持 16 进制、8 进制、2 进制表示
let n2 = 0xFFFFFFFFN
let n3 = 0o77777777N
let n4 = 0b1111111100000000N
let n5 = 1234_4567_91011N

// 当明确是一个 BigInt 类型时,字面量不用加 N 后缀
let n6 : BigInt = 1000000000000000000

//模式匹配也支持`BigInt`:

match 10N {
1N => println(1)
10N => println(10)
100 => println(100)
}
  • 支持了 enum 形式的错误类型声明,比如 Json 包中的错误类型可以如下声明:
pub type! ParseError {
InvalidChar(Position, Char)
InvalidEof
InvalidNumber(Position, String)
InvalidIdentEscape(Position)
} derive(Eq)

此外,labeled argumentmutable argument 也可以在 enum 形式的错误类型中使用,使用方式与普通的 enum 类型一致,比如:

type! Error1 {
A
B(Int, ~x: String)
C(mut ~x: String, Char, ~y: Bool)
} derive(Show)

fn f() -> Unit!Error1 {
raise Error1::C('x', x="error2", y=false)
}

fn g() -> Unit!Error1 {
try f!() {
Error1::C(_) as c => {
c.x = "Rethrow Error2::C"
raise c
}
e => raise e
}
}

fn main {
println(g?()) // Err(C(x="Rethrow Error2::C", 'x', y=false))
}
  • 添加了 catch! 语法,用于在 error handler 中将未处理的错误重新抛出,以简化错误处理,比如上面例子中的函数 g 可以重写为:
fn g() -> Unit!Error1 {
try f!() catch! {
Error1::C(_) as c => {
c.x = "Rethrow Error2::C"
raise c
}
}
}
  • 移除了 JavaScript 后端生成的代码对 TextDecoder API 的依赖。随着 Node.js 对 TextDecoder 的支持变得更完善,我们未来仍然可能会考虑在项目中引入和使用 TextDecoder

IDE 更新

  • 修复在 web 版 vscode 插件上调试时无法加载核心库 core 中的源码问题,现在 MoonBit IDE上的调试功能已恢复可用。

  • IDE 支持在模式匹配中根据类型对构造器进行补全:

match (x : Option[Int]) {

// ^^^^ 这里会补全 `None` 和 `Some`
}

标准库更新

  • 新增 Iter2 类型,用于遍历有两个元素的集合,例如 Map,或是带着 index 遍历数组:
fn main {
let map = { "x": 1, "y": 2 }
map.iter2().each(fn (k, v) {
println("\{k} => \{v}")
})
}

Iter[(X, Y)] 相比,Iter2[X, Y] 有更好的性能,并且有 for .. in 循环的原生支持。

  • @json.JsonValue 类型被移动到 @builtin 包中,并重命名为 Json@json.JsonValue 现在是以 Json 的一个类型别名,所以这一改动是向后兼容的。

  • @builtin 中添加了 ToJson 接口,用于表示可以被转换为 Json 的类型。

构建系统更新

  • moon check|build|test 添加 -C/--directory 命令,与 --source-dir 参数等价,用于指定 MoonBit 项目的根目录,也即 moon.mod.json 所在目录。

  • 修改 moon.mod.json 中的 root-dirsource,这个字段用于指定模块的源码目录,source 字段的值也可以是多层目录,但是必须为 moon.mod.json 所在目录的子目录,例如:"source": "a/b/c"。

    引入该字段的目的是因为 MoonBit 模块中的包名与文件路径相关。例如,如果当前模块名为 moonbitlang/example,而其中一个包所在目录为 lib/moon.pkg.json,那么在导入该包时,需要写包的全名为 moonbitlang/example/lib。有时,为了更好地组织项目结构,我们想把源码放在 src 目录下,例如src/lib/moon.pkg.json,这时需要使用 moonbitlang/example/src/lib 导入该包。但一般来说我们并不希望 src 出现在包的路径中,此时可以通过指定"source": "src"来忽略src这层目录,便可仍然使用 moonbitlang/example/lib 导入该包。

工具链更新

  • MoonBit AI 增加explain新功能:点击 MoonBit 图标,选择 /explain 来解释代码。代码的解释会出现在右侧。代码生成完以后,可以通过点击👍/👎给我们关于生成质量上的反馈。

ai explain

weekly 2024-08-05

· 阅读需 6 分钟

MoonBit更新

  • JSON字面量支持array spread。
let xs: Array[@json.JsonValue] = [1, 2, 3, 4]
let _: @json.JsonValue = [1, ..xs]
  • 增加了类型别名的支持,主要是为了渐进式代码重构和迁移,而不是某种给类型简短名字的机制。例如,假设需要把 @stack.Stack 重命名为 @stack.T,一次性完成迁移需要修改大量使用 @stack.Stack 的地方,对于大项目很容易产生 conflict,如果有第三方包使用了 @stack.Stack,则会直接造成 breaking change。使用 type alias,则可以在重命名后留下一个 @stack.Stack 的 alias,使现有代码不会失效:
/// @alert deprecated "Use `T` instead"
pub typealias Stack[X] = T[X]

接下来,可以渐进式地逐步迁移对 @stack.Stack 的使用、给第三方用户时间去适配新的名字。直到迁移全部完成,再移除 type alias 即可。除了类型重命名,typealias 还可用于在包之间迁移类型定义等等

  • 增加了给 trait object 定义新的方法的支持:
trait Logger {
write_string(Self, String) -> Unit
}

trait CanLog {
output(Self, Logger) -> Unit
}

// 给 trait object 类型 `Logger` 定义新的方法 `write_object`
fn write_object[Obj : CanLog](self : Logger, obj : Obj) -> Unit {
obj.output(self)
}

impl[K : CanLog, V : CanLog] CanLog for Map[K, V] with output(self, logger) {
logger.write_string("Map::of([")
self.each(fn (k, v) {
// 使用 `Logger::write_object` 方法来简化
logger
..write_string("(")
..write_object(k)
..write_string(", ")
..write_object(v)
.write_string(")")
})
logger.write_string("])")
}
  • 【breaking change】在可能返回错误的返回值类型 T!E 中,错误类型 E 只能是使用 type! 关键字声明具体的错误类型,目前支持以下两种声明方式:
type! E1 Int   // error type E1 has one constructor E1 with an Integer payload
type! E2 // error type E2 has one constructor E2 with no payload

函数声明中可以使用上述具体的错误类型来进行标注,并通过使用 raise 来返回具体的错误,比如

fn f1() -> Unit!E1 { raise E1(-1) }
fn f2() -> Unit!E2 { raise E2 }
  • 添加了内置的 Error 类型作为默认的错误类型,可以在函数声明中使用以下几种等价的声明方式来表明函数会返回 Error 类型的错误,比如:
fn f1!() -> Unit { .. }
fn f2() -> Unit! { .. }
fn f3() -> Unit!Error { .. }

对于匿名函数和矩阵函数,可以通过使用 fn! 来标注该函数可能返回 Error 类型的错误,比如

fn apply(f: (Int) -> Int!, x: Int) -> Int! { f!(x) }

fn main {
try apply!(fn! { x => .. }) { _ => println("err") } // matrix function
try apply!(fn! (x) => { .. }) { _ => println("err") } // anonymous function
}

通过 raisef!(x) 这种形式返回的具体的错误类型可以向上 cast 到 Error 类型,比如

type! E1 Int
type! E2
fn g1(f1: () -> Unit!E1) -> Unit!Error {
f1!() // error of type E1 is casted to Error
raise E2 // error of type E2 is casted to Error
}

错误类型可以被模式匹配,当被匹配的类型是 Error 的时候,模式匹配的完备性检查会要求添加使用 pattern _ 进行匹配的分支,而当其是某个具体的错误类型的时候则不需要,比如

type! E1 Int
fn f1() -> Unit!E1 { .. }
fn f2() -> Unit!Error { .. }
fn main {
try f1!() { E1(errno) => println(errno) } // this error handling is complete
try f2!() {
E1(errno) => println(errno)
_ => println("unknown error")
}
}

此外,在 try 表达式中,如果使用了不同种类的错误类型,那么整个 try 表达式可以返回的错误类型会按照 Error 类型进行处理,比如

type! E1 Int
type! E2
fn f1() -> Unit!E1 { .. }
fn f2() -> Unit!E2 { .. }
fn main {
try {
f1!()
f2!()
} catch {
E1(errno) => println(errno)
E2 => println("E2")
_ => println("unknown error") // currently this is needed to ensure the completeness
}
}

我们会在后续的版本中对此进行改进,以使得完备性检查可能更加精确

  • 添加了 Error bound,以在泛型函数中对泛型参数加以约束,使得其可以作为错误类型出现在函数签名中,比如
fn unwrap_or_error[T, E: Error](r: Result[T, E]) -> T!E {
match r {
Ok(v) => v
Err(e) => raise e
}
}

标准库更新

  • Bigint 变为builtin类型

构建系统更新

  • 支持 debug 单个.mbt文件

  • moon test支持包级别的并行测试

  • moon.mod.json增加root-dir字段,用于指定模块的源码目录,只支持指定单层文件夹,不支持指定多层文件夹。moon new会默认指定root-dirsrc,exec 和 lib 模式的默认目录结构变为:

exec 
├── LICENSE
├── README.md
├── moon.mod.json
└── src
├── lib
│ ├── hello.mbt
│ ├── hello_test.mbt
│ └── moon.pkg.json
└── main
├── main.mbt
└── moon.pkg.json

lib
├── LICENSE
├── README.md
├── moon.mod.json
└── src
├── lib
│ ├── hello.mbt
│ ├── hello_test.mbt
│ └── moon.pkg.json
├── moon.pkg.json
└── top.mbt

工具链更新

  • MoonBit AI 支持生成文档

ai file

weekly 2024-07-29

· 阅读需 4 分钟

MoonBit 更新

  • f(x)!! 修改为 f?(x),用于捕获函数调用中可能出现的错误,并返回 Result 类型
fn div(x: Int, y: Int) -> Int!String {
if y == 0 {
raise "division by zero"
}
x / y
}

fn main {
let a = div?(10, 2)
println(a) // Ok(5)
}
  • 新增 json 字面量支持。数字、字符串、数组和字典字面量为 json 类型提供了重载,可以在知道需要一个 json 类型时用于构建/匹配 json:
fn json_process(x : @json.JsonValue) -> Double {
match x {
{
"outer": {
"middle": {
"inner": [
{ "x": Number(x) },
{ "y": Number(y) }
]
}
}
} => x + y
_ => 0
}
}

fn main {
let x : @json.JsonValue = {
"outer": { "middle": { "inner": [{ "x": 42 }, { "y": 24 }] } },
"another_field": "string value",
}
json_process(x) |> println
}
  • 新增 derive(Hash) 支持,可以自动为类型定义生成 Hash trait 的实现:
enum MyType {
C1
C2(Int)
} derive(Eq, Hash)

fn main {
let m = {}..set(C1, 1)..set(C2(1), 2)..set(C2(2), 3)
println(m[C1]) // Some(1)
println(m[C2(1)]) // Some(2)
println(m[C2(3)]) // None
}
  • Js 后端生成代码移除对 TextDecoder 的依赖

标准库更新

  • 多个函数签名更改

    • Array::new_with_index-> Array::makei

    • FixedArray::new/FixedArray::new_with_index-> FixedArray::makei

    • FixedArray::empty -> FixedArray::new

  • 移除了 Debug trait

构建系统更新

  • 支持运行单个 .mbt 文件(不在某个包中),用法:moon run xx.mbt(默认链接标准库,便于运行一些代码片段,xx.mbt 中需要有 main 函数)

  • moon 支持 shell-completion

    • 使用示例:Zsh

Zsh 在启动时会自动读取 $FPATH 下的所有脚本,可以把补全脚本放在 $FPATH 下的任何一个路径中。

为了方便管理,我们新建一个专门放置补全的路径 $ZSH/completions 并加到 $FPATH

mkdir $ZSH/completions
echo "FPATH+=$ZSH/completions" >> ~/.zshrc
# Zsh 的 shell completion 文件名通常以下划线开头
moon shell-completion > $ZSH/completions/_moon # 一般不需要显式指明 shell 类型,自动读取 $SHELL

. ~/.zshrc # 重载即可使用,或 omz reload

如果希望保持 portability,可以直接把下面这行写进 .zshrc

eval "$(moon shell-completion --shell=zsh)"

shell-completion.gif

达到上图的效果还需要安装 zsh-autocomplete zsh-autosuggestions 两个插件。

工具链更新

  • moonfmt 修复 array spread 格式化后被加上花括号的问题;调整了括号推断的模式匹配,避免使用 wildcard

  • VS Code 现在支持自动对函数生成测试,包括黑盒测试(以 _test.mbt 结尾) 和白盒测试(单元测试)

  • 支持单个 .mbt 文件的 Run | Debug 按钮

weekly 2024-07-22

· 阅读需 8 分钟

MoonBit 更新

  • 【breaking change】错误处理的语法从f(x)! 改为了 f!(x),这样做以便于后续 IDE 在补全的过程中将函数名称和 ! 一起进行补全,并且对 f! 进行特殊的渲染

  • 【breaking change】local 函数如果可能返回错误类型,则必须对其进行标注,比如:

fn toplevel() -> Unit!String {
fn local1() -> Unit!String { raise "err" } // local function
fn local2() -> _!_ { raise "err" } // local function
apply!(fn (_x) -> Int!String { raise "err" }, 42) |> ignore // anonymous function
}

fn apply(f: (Int) -> Int!String, x: Int) -> Int!String {
f!(x)
}

同时,提供了 fn!fn local!() 语法用于标注该函数可能返回错误,并让编译器对返回的错误类型进行推导,其中前者用于矩阵函数和匿名函数,后者用于普通的 local 函数,比如:

fn toplevel() -> Unit!String {
fn local!() { raise "err" } // local function
apply!(fn! (_x) { raise "err" }, 42) |> ignore // anonymous function
apply!(fn! { _ => raise "err" }, 42) |> ignore // matrix function
}
  • 完善了 try 表达式的功能,目前完整的 try 表达式的使用方式如下:
fn test_try() -> Unit {
fn f() -> Int!String {
raise "err"
}
try {
println("this is try body")
f!()
} except {
err => println(err)
} else {
val => println(val)
}
}

其中 except 关键字用于处理 try body 返回错误的情况,else 关键字用于处理 try body 正常返回的情况,同时改语法有如下几种简化情况:

  • 如果 else 分支中不需要对返回值进行任何处理,则 else 分支可以缺省

  • try body 可以是一个简单的表达式

  • except 关键字可以缺省

比如下述程序所示:

fn test_try() -> Result[Int, String] {
fn f() -> Int!String {
raise "err"
}
try Ok(f!()) { err => Err(err) }
}
  • 对模式匹配也添加了整数字面量的重载支持:
fn is_42(x : Double) -> Bool {
match x {
42 => true
_ => false
}
}
  • 【即将到来的 breaking change】Map pattern 将会在本周更改{ "key" : value }中的语义匹配。

原本的 map pattern 中,{ "key" : value } 里,value 的类型为 Option[_],无论 "key" 是否存在都会匹配。接下来,pattern 中 { "key": value } 的语义会修改为匹配 "key" 存在的情况,value 也会直接匹配 "key" 的实际内容。如果想要同时匹配 "key" 存在/不存在的情况,需要使用一个 { "key"? : value_or_none } 的新写法,其中 value_or_none 的类型为 Option[_]

  • 【即将到来的 breaking change】显式实现 trait 的旧语法 fn Trait::method(...) 将会在本周被移除,现有代码应当改用语法 impl Trait for SelfType with method(...)。未来 fn Trait::method(...) 这一语法会被用于直接给 trait object 类型定义方法

MoonBit IDE 更新

  • 现在 main 函数上将提供 Run | Debug 的 Codelens,便于快捷运行和调试程序。目前快捷调试仅支持 JavaScript 后端。

ide

  • 现在顶层函数支持通过 Codelens 调用 AI 生成测试。不过目前这个功能尚在开发,并不稳定。

    1. 点击 “Gen test” 按钮。

    step1

    1. 右边会出现一个新的窗口显示生成好的测试。

    step2

    1. 点击 “Reject” 来去掉不希望的测试,点击 “insert” 将测试插入到源文件中。

标准库(moonbitlang/core)更新

  • 修改了 Show trait 的定义,使其有更正确的行为。新的 Show trait 的定义如下:
pub trait Show {
// `output` 函数用于实现复合类型的 `Show`。
// 它把 `Self` 的一个字符串表示写入到一个 `Logger` 中
output(Self, Logger) -> Unit
// `to_string` 是给 `Show` 的用户使用的,
// 它可以用于字符串插值和 `println` 等。
// `to_string` 有一个使用 `output` 的默认实现。
// 有些类型,如 `String`,会自行重载 `to_string` 以修改其行为
to_string(Self) -> String
}

新的 Show 解决了 String 的实现行为不正确的问题。过去,如果一个复合类型中有 String,那么它的 Show 的实现不会对这个 String 进行 escape,这会导致错误的结果。另一方面,我们希望当 String 直接被用于字符串插值和 println 时,不需要 escape。新的 Show 定义及 core 中的实现能够同时满足上述需求。

对于 Show 的实现者,需要注意以下几点:

  • 应当实现 output 方法,而不是 to_string 方法

  • 在递归调用子结构(如结构体的字段)的 Show 实现时,应当使用 Show::output(..., logger),而不是 to_string

  • Show::to_string 的默认实现不是指一个方法 ,因此无法使用 . 调用。如果想支持 .to_string() 这样的写法,可以添加一个如下的辅助定义:

pub fn to_string(self : ...) -> String {
Show::to_string(self)
}
  • 【即将到来的 breaking change】Show trait 的定义修改后,Debug trait 的功能已经完全被 Show trait 覆盖了。因此,Debug trait 已经被弃用,并且会在近期被删除。

  • 数据结构类型名称被改为了 T, 修改样例如下

// old
// let m : @hashmap.HashMap[Int, Int] = @hashmap.new()

// new
let m : @hashmap.T[Int, Int] = @hashmap.new()

以下数据结构类型均被修改为 T

  • deque
  • hashmap
  • hashset
  • immut/array
  • immut/hashmap
  • immut/hashset
  • immut/list
  • immut/priority_queue
  • immut/sorted_map
  • immut/sorted_set
  • queue
  • priorityqueue
  • rational
  • sorted_map
  • sorted_set

构建系统更新

  • 调整了构建过程中的输出,现在只会在某条命令执行失败的情况下会打印出该命令

  • moon check|build|test|bundle--target 之前只支持三种选项 wasm|wasm-gc|js,修改后支持同时指定多个后端并行执行。支持选项变更为 wasm|wasm-gc|js|all,并且可以用逗号任意组合。添加 --serial 选项将不同后端之间的并行执行变为串行执行,但是同一后端内部仍为并发执行。使用样例:

    • moon test --target all执行全部后端的测试

    • moon test --target js,wasm 执行 js,wasm 后端的测试

    • moon test --target js,wasm --serial等价于按顺序执行 moon test --target js; moon test --target wasm

  • moon.pkg.json["link"]["wasm"|"wasm-gc"] 中添加与内存相关的 heap-start-addressimport-memory 配置

{ 
"link": {
"wasm": {
"heap-start-address": 65536,
"import-memory": {
"module": "xxx",
"name": "yyy"
}
}
}
}
  • moon new 默认协议修改为 Apache-2.0

weekly 2024-07-15

· 阅读需 6 分钟

MoonBit 更新

  • 新增级联运算符(cascade operator):MoonBit 引入了 “..” 操作符,能够优雅地对可变 API 进行链式调用,同时保持可变 API 签名的整洁(依然返回 Unit)。

如图例,为了避免重复键入上图中的array, 和区分不可变与可变操作,MoonBit引入了级联运算符。对于一个类型中的所有返回Unit的方法,可以使用..将这些方法的调用串联起来,而不需要专门修改这些方法的返回类型。 array..push(5)..sort()相当于依次调用了可变操作array.push(5)array.sort(), 最终返回array

cascade.png

  • 正式移除了@package.Type::[...]的语法。推荐使用@package.of([...])作为替代。

  • 现在--target js下调试模式生成的sourcemap支持names表。因此使用 devtools 或 vscode 进行调试时,局部变量会显示为原变量名,还可以在Debug console中使用原变量名访问它。这极大的改进了调试体验。

Before: names_before.png

After: names_after.png

  • 数组支持展开语法 (spread syntax),可以在数组构造时,将任何支持iter()方法的对象表达式在语法层面展开成一组 Iter[_] 对象。

    数组表达式:

let u1 = [1, 2, 3]
let u2 = [5, 6, 7]

fn main {
let v = [..u1, 4, ..u2, 8]
println(v)
}

// [1, 2, 3, 4, 5, 6, 7, 8]

Map表达式(返回键值对):

  let map : @sorted_map.T[Int,String] = @sorted_map.of([(3, "c"), (2, "b"), (1, "a")])

fn main {
let v = [..map, (4, "d")]
println(v)
}

// [(1, a), (2, b), (3, c), (4, d)]
  • 支持了和C语言类似的位运算符(^, &, |, <<, >>),优先级同C一致。同时为了避免歧义,当表达式同时存在几个运算符并且不容易区分优先级时,formatter会插入额外的括号以改善可读性。

  • 支持重载整数字面量。在已知类型时,Int 以外的类型的字面量可以省略 UL 等特殊标记:

fn main {
let uint : UInt = 42
let int64 : Int64 = 42
let double : Double = 42
}

标准库(moonbitlang/core)更新

  • Hash Trait 即将更新为如下形式。
trait Hash {
hash_iter(Self, Hasher) -> Unit
hash(Self) -> Int
}

impl Hash with hash(self) {
let hasher = Hasher::new()
hasher.hash_iter(self)
hasher.finalize()
}

构建系统更新

测试机制调整说明

为优化黑盒测试的开发体验,moon 现支持自动将以 _bbtest.mbt 结尾的源代码文件封装为黑盒测试,moon 在编译 *_bbtest.mbt 时会自动将其所在的包作为依赖。为了更好地开发和测试 MoonBit 项目,接下来将对 MoonBit 项目的测试机制进行调整。

Background

目前一个 MoonBit 项目中可以有三种类型的测试:白盒测试(white box test)、黑盒测试(black box test)、内部测试(inline test)。

  • 白盒测试:写在 *_test.mbt 中,构建系统会把当前包中的 *.mbt*_test.mbt 一起打包编译,因此在 *_test.mbt 中可以访问当前包的私有成员,这些测试可以使用 moon.pkg.jsonimporttest-import 字段中的依赖。test-import 只在白盒测试中用到,不会打包到最终的构建产物中。

  • 黑盒测试:写在 *_bbtest.mbt 中,构建系统会在编译 *_bbtest.mbt 时会自动将其所在的包作为依赖,*_bbtest.mbt只能访问其所在包的公开成员(即模拟外部用户在使用这个包时的视角),这些测试可以使用 moon.pkg.json 中的 bbtest-import 字段中的依赖(以及其所在包,不需要显式写在bbtest-import 字段中)。bbtest-import 只在黑盒测试中用到,不会打包到最终的构建产物中。

  • 内部测试:可以直接写在 *.mbt(注意这里的 *.mbt 不包含上面提到的 *_test.mbt*_bbtest.mbt)中,可以访问当前包的私有成员,这些测试只使用 moon.pkg.jsonimport 字段中的依赖。

Change

一些命名上的调整:当前的*_test.mbt(白盒测试)后续将调整为*_wbtest.mbt,*_bbtest.mbt(黑盒测试)将调整为 *_test.mbt,鼓励大家写黑盒测试。

工具链更新

  • moonfmt改进

    • 现在当源码中存在语法错误时,格式化会忽略相关的代码,此时仍然能够执行代码格式化。

    • 当表达式同时存在几个运算符并且不容易区分优先级时,格式化会插入额外的括号以改善可读性。

  • IDE 增加对黑盒测试(*_bbtest.mbt)的支持