跳到主要内容

2024-12-02

· 阅读需 4 分钟

MoonBit更新

  • 添加了 range pattern 的支持,可以在模式匹配中匹配一个区间的整数值和字符。

Range pattern 的语法为 a..<b(不包含上界 b)或 a..=b(包含上界 b)。上下界可以是字面量、用 const 声明的常量,或是 _,表示在这一侧没有约束:

const Zero = 0
fn sign(x : Int) -> Int {
match x {
_..<Zero => -1
Zero => 0
Zero..<_ => 1
}
}

fn classify_char(c : Char) -> String {
match c {
'a'..='z' => "lowercase"
'A'..='Z' => "uppercase"
'0'..='9' => "digit"
_ => "other"
}
}
  • 允许用 x.f(...) 的语法调用 trait 的实现
trait I {
f(Self) -> Unit
}

type MyType Int
impl I for MyType with f(self) { println(self._) }

fn main {
let my : MyType = 42
my.f()// 输出 42
}

假设 x 的类型是 T,那么目前 x.f(...) 语法的解析规则如下:

  1. 如果 T 有方法 f,调用方法 T::f
  2. 如果在 T 所在的包内有 trait 实现 impl SomeTrait for T with f,那么调用 SomeTrait::f。如果有多个满足要求的 f,编译器会报一个歧义的错误
  3. 如果上述两条规则都没有找到 f,编译器会在当前包内搜索 impl SomeTrait for T with f。注意这一条规则只在当前包内适用,假如 T 不是当前包定义的,外部就无法用 . 调用 T 在当前包的 impl

这些规则在增加了 MoonBit 的 . 语法的灵活性的同时,维持了语义的清晰性和较好的重构安全性。解析 x.f(...) 时只会搜索 x 的类型 T 所在的包和当前包,而不是所有被引入的依赖。这保证了引入新的依赖不会使现有代码出错或改变行为。

  • 新增trait alias

没有引入新的语法,trait alias 的写法和 type alias 一样:

typealias MyShow = Show
impl MyShow for MyType with ...

IDE更新

  • Web IDE 新增更新 inspect 测试的功能。 try-inspect.gif

  • MoonBit AI 支持在已生成的代码中进行修改,修改后的代码会自动进行语法和类型检查。 ai-modify.gif

  • 修复文档注释中 markdown 代码块的高亮显示问题。

构建系统更新

  • moon check支持传递 warn list。

  • moon test支持运行项目文档注释中的测试。

    • 用法:moon test --doc,运行当前项目文档注释中的所有测试;
    • 测试必须标注在两行 ``` 之间, 如: moon-test.png
  • moon fmt 修复关于 local 函数、return 表达式的格式化。

  • moon fmt 修复数组下标语法附近的注释在格式化后错位的问题。

  • moon fmt 默认启用 -block-style

文档更新

2024-11-18

· 阅读需 6 分钟

MoonBit更新

  • trait 新增 abstract 和 pub(readonly) visibility
  1. 在当前包内,abstract/readonly trait 和一个完全公开的 trait 表现相同;
  2. 在当前包外不能直接调用 abstract trait 里的方法,且不能给 abstract trait 写新的实现;
  3. pub(readonly) 的 trait 在外部不能实现,但可以直接调用方法;
  4. trait 的默认可见性从私有变成 abstract。定义一个私有 trait 要写 priv trait
  5. 实现一个 abstract/readonly trait 时,如果想要外部也可以使用这个实现,至少有一个方法需要用 impl Trait for Type 的形式实现。

下面是一个 abstract trait 的例子:

trait Number {
add(Self, Self) -> Self
}

pub fn add[N : Number](x : N, y : N) -> N {
Number::add(x, y)
}

impl Number for Int with add(x, y) { x + y }
impl Number for Double with add(x, y) { x + y}

使用 moon info 生成 .mbti 接口文件,可以看到上述定义对外的接口是:

trait Number
impl Number for Int
impl Number for Double

fn add[N : Number](N, N) -> N

因此,外部只能对 IntDouble 调用 add 函数。编译器会保证 Number 在任何时候都只能被 IntDouble 实现。

  • 调整类型和 trait 的可见性

为了鼓励使用 readonly 的类型/trait、提供更健壮的 API,我们计划在未来将类型定义和 trait 的 pub 可见性的语义修改为默认 readonly。如果想要获得完全公开的可见性,类型定义需要写 pub(all) struct/enum/type,trait 需要写 pub(open) trait(表示这个 trait 可以接受外部的实现)。

目前,pub 的语义依然是完全公开的可见性,但编译器会输出一个警告提示用户将 pub 迁移至 pub(all)/pub(open)。已有的代码可以通过 moon fmt 自动迁移,formatter 会把 pub 自动变成 pub(all)/pub(open)

  • struct 类型新增 private field 功能,可以在一个公开的 struct 的某些 field 前用 priv 关键字隐藏它:
  1. 在外部,priv field 是完全不可见的。不能读也不能修改
  2. 如果一个 struct 有 private field,它在外部不可以用字面量直接构造。但可以使用 struct update 语法 { ..old_struct, new_field: ... } 来更新公开的 field
  3. 迁移到后缀风格的 label argument 语法。新的语法相当于把~移动到了标识符之后,如果是~label?这种形式,波浪号可以省略。
enum Foo {
Bar(label~ : Int, Bool)
}
fn f(a~ : Int, b~ : Int = 0, c? : Int) -> Unit {...}
fn main {
f(a=1, b=2, c=3)
f(a=1, c?=Some(3))//b 声明了默认值可以省略,c 转发 option 语法照旧
let a = 1
let c = Some(3)
f(a~, c?) //和声明处一样,punning 也变成了后缀的形式
}

旧的语法将在未来移除:

enum Foo {
//deprecated
Bar(~label : Int, Bool)
}
//deprecated
fn f(~a : Int, ~b : Int = 0, ~c? : Int) -> Unit {}
fn main {
let a = 1
let c = Some(3)
f(~a, ~c?) //deprecated
}

已有的代码可以通过 moon fmt 进行迁移。

  • 添加了一系列保留字,供编译器未来使用。目前,使用保留字会得到一个警告,提示用户进行迁移。未来这些保留字可能会变成语言关键字。本次新增的保留字为:
module
move
ref
static
super
unsafe
use
where
async
await
dyn
abstract
do
override
typeof
virtual
yield
local
method
alias
  • 我们计划将标准库中的 Bytes 类型改为不可变类型,因此会将一些对 Bytes 类型进行修改的函数标记为 deprecate,如果需要使用可以修改的 Bytes 类型,可以考虑使用 Array[Byte], FixedArray[Byte] 或者 Buffer 进行替代。

  • @test.is 函数被重命名为 @test.same_object,旧的名字被 deprecate。未来我们将把 is 设为保留字。

IDE更新

  • 修复了moon fmt --block-style没有正确处理///| comment的问题。

  • IDE 适配后缀 label argument 语法,支持 gotoref 和 rename。

  • 文档中支持代码高亮。

  • 修复 lsp 在@补全时有 internal package 的问题。

构建系统更新

  • -verbose选项现在会输出当前运行的命令。

MoonBit ProtoBuf

2024-11-04

· 阅读需 5 分钟

MoonBit更新

  • 增加了编译期常量的支持。常量的名字以大写字母开头,用语法 const C = ... 声明。常量的类型必须是内建的数字类型或 String。常量可以当作普通的值使用,也可以用于模式匹配。常量的值目前只能是字面量:
const MIN_INT = 0x1000_0000
const MAX_INT = 0x7fff_ffff

fn classify_int(x : Int) -> Unit {
match x {
MIN_INT => println("smallest int")
MAX_INT => println("largest int")
_ => println("other int")
}
}

fn main {
classify_int(MIN_INT) // smallest int
classify_int(MAX_INT) // largest int
classify_int(42) // other int
}
  • 改进了 unused warning,增加了一系列功能:
  1. 增加了 enum 的参数未被使用的检测
enum E {
// 现在编译器会提示 y 未被使用
C(~x : Int, ~y : Int)
}

fn f(x : E) -> Unit {
match x {
C(~x, ..) => println(x)
}
}

fn main {
f(C(x=1, y=2))
}
  1. 增加了函数的默认参数的默认值未被使用的检测(默认关闭)
// f 是一个私有函数,而且每次调用它时,调用者都显式给 x 提供了值。
// 如果开启了警告 32(默认关闭),编译器会提示 x 的默认值未被使用
fn f(~x : Int = 0) -> Unit {
println(x)
}

fn main {
f(x=1)
}
  • 支持从其他包中直接导入函数,直接导入的函数在使用时,不需要 @pkg. 的前缀,使用方式需要通过在 moon.pkg.json 配置文件中,通过 "value" 字段声明,比如下面这个示例:
{
"import": [
{
"path": "moonbitlang/pkg",
"alias": "pkg",
"value": ["foo", "bar"]
},
]
}

这里例子中,在 moon.pkg.json 中声明了从 moonbitlang/pkg 导入 foobar 两个函数,从而在使用这两个函数的时候可以直接对其进行调用,而不需要再写 @pkg.foo 或者 @pkg.bar

  • 现在 BigInt 类型在 JavaScript 后端下会编译到原生的 BigInt,对 BigInt 的 pattern matching 也会编译为高效的 switch 语句。

  • 实验性功能:现在 JavaScript 后端会根据 moon.pkg.json 中指定的导出函数配置来生成 .d.ts 文件,用于改进在 TypeScript/JavaScript 侧使用 MoonBit 生成的 JavaScript 代码的体验。(目前复杂类型的导出还在设计中,现阶段生成为 TS 的 any 类型)

IDE更新

  • block-line 标记支持

    增加对顶层注释///中的特殊标记block-line///|的支持。block-line用于分割顶层的代码块,使用它可以提升代码的可读性和维护性。通过在顶层代码块之间添加标记,IDE能够更清晰地展示代码结构。

block-line.jpg

通过moon fmt --block-style可以自动在每个顶层成员之前添加这样的标记。未来基于block-line的增量式代码解析和类型检查将进一步提高LSP的反应速度和可用性,提升开发效率。

  • 在线 IDE 现已支持访问 GitHuB 仓库,
  1. 打开 GitHub 中的 MoonBit 项目
  2. 将 github.com 更改为 try.moonbitlang.com
  • test explorer 中支持 test coverage 可视化。

coverage.jpg

  • AI添加了/doc-pub命令来为公开函数生成文档。

  • 修复了/doc命令会覆盖pragmas的问题。

  • patch 功能现在能够验证生成出来的 test case 是否正确。

ai-test.jpg

构建系统

  • moon check ⽀持指定包,指定后会 check 当前包和它的依赖;用法: moon check /path/to/pkg

MoonBit Markdown 库

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