跳到主要内容

2025-01-13

· 阅读需 6 分钟

语言更新

  • 实验性的异步支持。我们添加了实验性的异步编程支持。可以用 async fn ... 声明异步函数,用 f!!(...) 调用异步函数。同时 MoonBit 提供了一些原语用于中断控制流。更多信息见 docs/async-experimental

    目前异步标准库和事件循环仍在开发中,因此如果要使用异步编程功能,复用 JavaScript 后端的事件循环与 Promise API 会更轻松。目前异步编程相关功能的设计都是实验性质的,未来可能会根据反馈发生不兼容改动。我们欢迎并感激对异步编程功能的试用和反馈。

  • 在本周稍后,我们将对方法的语义进行一次大的调整,以简化方法相关的规则。目前,MoonBit 对方法的调用语法的规则是:

    • 方法可以用 fn f(self : T, ..)fn T::f(..) 的形式声明

    • 第一个参数为 Self 的方法,可以用 xx.f(..) 的形式调用

    • 如果没有歧义,可以用 f(..) 的形式直接调用方法 但上述最后一条规则比较混沌,且缺乏用户控制。此外,调用语法(f(..) 和声明语法(T::f(..))之间缺乏一致性。接下来,我们将对方法的语义进行调整:

    • 方法可以用 fn f(self : T, ..) 的形式声明。这种方法既可以用 xx.f(..) 的形式调用,也可以当成普通函数调用(f(..))。这种方法和普通函数处于同一个命名空间,不能重名

    • 方法也可以用 fn T::f(..) 的形式声明。这种方法可以用 xx.f(..) 的形式调用,也可以用 T::f(..) 的全名形式调用,但不能当作普通函数用 f(..) 的形式调用。这种方法可以和普通函数重名

    这一设计背后的直觉是:所有 fn f(..) 形式的定义都是普通函数,而所有 fn T::f(..) 形式的定义都会被放在 T 代表的一个小命名空间内。

    对于第一个参数不是 Self 的方法,例如 new,如果希望用 new(..) 的形式直接调用,可以将其直接定义为一个普通函数。在新的语义下,我们预期的库作者的 API 设计思路是:

    • 如果某个函数在当前包内没有歧义,就用 fn f(..) 的形式定义它。如果希望用 xx.f(..) 的方式调用它,就把第一个参数命名为 self

    • 如果某个函数在当前包内有歧义,就把它定义成 fn T::f(..),放进一个小命名空间里,来规避歧义。调用者必须用 T::f(..)xx.f(..) 的形式来调用。

  • 更多的alert支持,包括:

    • ⽀持对类型的使⽤触发 alert;

    • ⽀持对具体的constructor的使⽤触发 alert。

  • 对 ArrayView/BytesView/StringView 的构造和模式匹配进行了一些改进:

    • 允许了取 view 操作使⽤负数下标,比如:
fn main {
let arr = [1, 2, 3, 4, 5]
let arr_view = arr[-4: -1]
println(arr_view) // [2, 3, 4]
let arr_view_view = arr_view[-2:-1]
println(arr_view_view) // [3]
}
  • ⽀持了对 Bytesview 的模式匹配,并且允许 pattern ⾥⾯出现 byte literal,比如:
fn f(bs: BytesView) -> Option[(Byte, BytesView)] {
match bs {
[b'a' ..= b'z' as b, .. bs] => Some((b, bs))
_ => None
}
}

///|
fn main {
let s = b"hello"[:]
let r = f(s)
println(r) // Some((b'\x68', b"\x65\x6c\x6c\x6f"))
}
  • 在 array pattern ⾥⾯允许省略 as 关键字,可以写成 [a, .. rest, b],上面的例子中已经出现了这种用法,同时 [a, .. as rest, b] 这种写法也会继续支持,不过 formatter 会自动把中间的 as 关键字省略掉。

IDE 更新

  • 新增 toggle multi-line string 的 command。
  • Workspace symbols 增加了更多的信息,便于搜索某个具体的函数和类型。

构建系统更新

  • 修复 doc test 更新多行字符串测试结果的 bug。

文档更新

  • MoonBit Tour 支持 debug codelens,默认开启值追踪。

  • Moonbit OJ 用户可以在 oj 平台上刷题并兑换奖品。

oj.png

标准库更新

  • 改变了 Int64JSON 转换的行为。原本通过 Double 转换会损失精度,现改为通过字符串保存。

2024-12-30

· 阅读需 5 分钟

语言更新

  • 新增labeled loop语法,可在多层循环中直接跳转到指定的某一层,label使用 ~ 作为后缀
fn f[A](xs : ArrayView[A], ys : Iter[Int]) -> @immut/list.T[(Int, A)] {
l1~: loop 0, xs, @immut/list.Nil {
_, [], acc => acc
i, [x, .. as rest], acc =>
for j in ys {
if j == i {
continue l1~ i + 1, rest, @immut/list.Cons((j, x), acc)
}
if j + i == 7 {
break l1~ acc
}
} else {
continue i - 1, rest, acc
}
}
}
  • 新增discard argument, 以单个下划线命名的函数参数会被丢弃,同一函数内可以丢弃多个参数。Discard argument 只支持位置参数,暂不支持命名参数。
fn positional(a : Int, b : Int) -> Int {
a + b
}

fn discard_positional(_: Int, _: Int) -> Int {
1
}
  • pub 的语义正式从完全公开切换为只读,同时 pub(readonly) 语法被 deprecate。如果想要声明一个完全公开的 type/struct/enum,需要写 pub(all),如果想要声明一个完全公开(外部可以实现)的 trait,需要写 pub(open)。这一改动已提前通过 warning 的形式进行预告,之前使用 pub 会报 warning。如果已经按照 warning 将 pub 改为 pub(all)/pub(open),本次只需将 pub(readonly) 改为 pub 即可完成迁移。moon fmt 能自动完成 pub(readonly) -> pub 的迁移。

  • 未来,trait 找不到实现时 fallback 到方法的行为 有可能 被移除。我们鼓励新代码使用显式的 impl Trait for Type 语法而非方法来实现 trait。在无歧义时,impl Trait for Type with method(...) 也可以使用 dot syntax 调用,因此使用显式 impl 不会损失使用的便利性。

  • 移除了带标签参数旧的前缀语法。

  • 移除了用于获取 newtype 内容的旧语法 .0。同时如果一个 newtype 内的类型是 tuple,现在 .0.1 这些 index access 能自动转发到 newtype 内的 tuple,例如:

type Tuple (Int, String)

fn main {
let t = (4, "2")
println(t.0) // 4
println(t.1) // 2
}
  • derive(FromJson)derive(ToJson) 加入了参数支持,可以控制类型序列化和反序列化过程的具体行为和数据布局。具体修改见 Docs > Language > Deriving

    目前可以对字段进行重命名和调整 enum 的序列化格式。JSON 序列化与反序列化过程的具体行为在未来可能会进行优化而发生改变。

enum UntaggedEnum {
C1(Int)
C2(String)
} derive(ToJson(repr(untagged)), Show)
// { "0": 123 }, { "0": "str" }

enum InternallyTaggedEnum {
C1(Int)
C2(String)
} derive(ToJson(repr(tag="t")), Show)
// { "t": "C1", "0": 123 }

enum ExternallyTaggedEnum {
C1(Int)
C2(String)
} derive(ToJson(repr(ext_tagged)), Show)
// { "C1": { "0": 123 } }

enum AdjacentlyTaggedEnum {
C1(Int)
C2(String)
} derive(ToJson(repr(tag="t", contents="c")), Show)
// { "t": "C1", "c": { "0": 123 } }

struct FieldRenameAllCamel {
my_field : Int
} derive(ToJson(rename_all="camelCase", repr(tag="t")), Show)
// { "t": "fieldRenameAllCamel", "myField": 42 }

struct FieldRenameAllScreamingSnake {
my_field : Int
} derive(ToJson(rename_all="SCREAMING_SNAKE_CASE", repr(tag="t")), Show)
// { "t": "FIELD_RENAME_ALL_SCREAMING_SNAKE", "MY_FIELD": 42 }

IDE更新

  • 增加了对于loop label的gotodef/gotoref/rename 等功能的IDE 支持。

  • Document symbol现在在IDE中会以有层级的方式显示,效果如下: layer.png

  • 修复了白盒测试有关类型重命名的bug。

  • 新增了在会造成重复的情况下自动省略 parameter inlay hints 的功能。

  • IDE增加值追踪功能,点击 main 函数上面的 Trace codelen 后启用,再次点击后关闭;

    • 对于循环体内的变量,只显示最新的值以及 hit 次数。 trace.png

构建系统更新

  • moon新增 --no-strip 参数,可在 release 构建模式下保留符号信息

文档更新

  • 修复了 MoonBit Tour 在切换页面时 theme 无法保持的问题

  • MoonBit Tour增加 range、range pattern 和 newtype 小节

2024/12/16

· 阅读需 4 分钟

MoonBit更新

  • Trait object 语法由原来的直接写 Trait 变更为 &Trait (旧语法已deprecate)。这一改动是为了让 trait object 类型和 trait 本身在语法上区分开,以避免混淆。在所有涉及 trait object 的场合,包括类型标注、为 trait object 定义方法(fn &Trait::method(...))和创建 trait object(... as &Trait)都要进行修改。

  • 新增了 local types 语言特性,目前支持在一个 toplevel function 中的顶部声明只在当前 toplevel function 中可见的 struct/enum/newtype,并且可以通过 derive 来为这些 local types 添加新的方法,比如:

fn toplevel[T: Show](x: T) -> Unit {
enum LocalEnum {
A(T)
B(Int)
} derive(Show)
struct LocalStruct {
a: (String, T)
} derive(Show)
type LocalNewtype T derive(Show)
...
}

注意 local type 可以使用当前 toplevel function 中的泛型参数,但自身不可以引入额外的泛型参数,local type 可以使用 derive 生成相关方法,但不能额外定义其他新的方法,local type 暂不支持声明 error 类型。

IDE更新

  • 修复一些 LSP 相关的 bug。

    • 错误类型在 hover 的时候,定义的类型和 payload 的类型会连在⼀起;
    • 修复把⼀个单文件加到 module 里(在同级文件夹下创建moon.pkg.json)再移出(将 moon.pkg.json 删除)后,LSP 对该文件不提供服务的问题;
    • 修复了 test-import-all 配置时好时坏的问题;
    • 修复 LSP 奇怪 inlay hint 的 bug。
  • 给 LSP 的 formatter 开启了 block-line 的选项

  • LSP 支持 warn-list 的配置。

  • 优化了 web IDE 的 debug 体验,用户打开 devtools 后点击 debug 会直接停在 main 函数上。

debug.gif

  • 允许 doctest 中用 test 封装,支持更新 inspect 以及 panic test
/// ```
/// test "panic test xxx" {
/// panic()
/// }
/// ```
  • MoonBit AI 实现了生成时候切换模型,中止,重试的功能。

ai.gif

构建系统更新

  • moon run 跑测试时的参数传递调整,支持直接传递 --stack-size 调整 v8 栈大小。

  • 【breaking】黑盒测试过程中会自动导入被测试的包中的 public definition,比如在测试 @json 这个包的黑盒测试中再使用 @json 这个包里的函数或者类型时,就无须再写上 @json 前缀,而是可以直接使用。如果需要关闭这个特性,则需要在 moon.pkg.json 中写明 "test-import-all": false

文档更新

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