MoonBit更新——0519

2025-05-19

语言更新

  • x..f(..) 的语义即将发生改变,在 ./… 调用链末尾的最后一个 … 以后会自动丢弃它的值。因此,下面的代码:
impl[X : Show, Y : Show] Show for (X, Y) with output(self, logger) {
  logger
  ..write_string("(")
  ..write_object(self.0)
  ..write_string(", ")
  ..write_object(self.1)
  // 原本,这里必须写 `.`,否则整个 `.` 链的类型是 `&Logger`,不符合预期类型 `Unit`
  .write_string(")")
}

以后可以简化成

impl[X : Show, Y : Show] Show for (X, Y) with output(self, logger) {
  logger
  ..write_string("(")
  ..write_object(self.0)
  ..write_string(", ")
  ..write_object(self.1)
  // 以后可以直接一路 `..` 到底了
  ..write_string(")")
}

但这也意味着直接使用 x..f() 的值的用法将会被废弃,需要显式保存x。例如,下面的代码:

let arr = []..push(1)..push(2)

需要改写成:

let arr = []
arr..push(1)..push(2)

moonbit-lang 枚举构造器和结构体的字段支持单独的文档注释,在补全时会显示相应的文档。

///| Location enum
struct Location {
  /// X coordinate
  x : Int
  /// y coordinate
  y : Int
}

///| Variant enum
enum Variant {
  /// Stirng constructor
  String(String)
  /// Number constructor
  Number(Double) 
}

@bytes.View@string.View 在 C 和 wasm1 后端现在会被编译成值类型,这意味着这两个类型不会引入内存分配,性能有较大提升

工具链更新

  • vscode 插件支持semantic token, 会对有effect的函数(会抛出异常的函数, 异步函数)调用使用不同的样式高亮.

  • 构建系统支持 virtual package 特性,通过将一个 package 声明为虚拟包,定义好一套接口,用户可选择具体使用哪一份实现,如不指定则使用该虚拟包的默认实现。通过这项特性,给分离接口与实现带来较大灵活性。注意:目前这项特性处于实验性状态。详情请查看:MoonBit 新特性:Virtual Package 虚拟包机制

  • 支持对于单个 .mbt 和 .mbt.md 文件的 test 和 debug codelen

2 个赞

寄. 问一下原来的写法,

{}..set(Input, f1)..set(Change, f2)

之前会返回最终的对象, 现在结果会被丢弃. 有什么简短的写法?

如果 key 都是字符串是有很简单的写法的, 但是这里 InputChange 都是 enum 里的 Constructor, 有什么语法? 也能自行做类型推断的.

我看着好像没影响呀,不是仍然可以根据下文进行自动推断吗?

///|
enum Event {
  Input
  Change
} derive(Eq, Hash)

///|
fn init {
  let f1 = fn() {  }
  let f2 = fn() {  }
  let a = {} // 可以自动推导出 Map[Event, () -> Unit]
  a..set(Input, f1)..set(Change, f2)
}

我需要的直接拿到这个表达式的返回值, 原来的返回值就是修改以后的 a. 现在这个返回值不是被丢弃了吗. 拿不到了.

我看你的意思应该是 respo.mbt/src/main/panel.mbt at main · Respo/respo.mbt · GitHub 这个仓库里头这种写法吧。

感觉可以考虑改成 [(Input, f1), (Change, f2)] 这样的表达式替代吧。

不过话说回来x..f()这个破坏性更新的问题,我觉得应该提供一个语法糖来向下兼容,比如x..self

是啊. 暂时用 Map::of([]) 应该可以. 不过 Map 本身有使用 {"a": 1} 这样的语法, 如果能对于任意数据类型用也省事. 试了试没试出来.

要不加个helper function吧:

fn events(click? : (Event) -> Unit, dbclick? : (Event) -> Unit, ...) -> Map[(EventType, Callback)] {
  ...
}

嗯我考虑下方法. 总体上高频用到的写法, 总还是指望短一点.

  1. 按照你的需求,非要使用Map的话,我自己感觉用 ..set 其实也不算符合直觉的追加方案,因为它是指令式地去构建map,不是一次性把内存非配好的感觉。
  2. 但像你说的,最好是使用 {"a": 1} 这样的语法(比如{ [Input]: 1 })。但目前没有“非字面量”的支持,所以短期内指望不了。
  3. 按照你的需求,前面提到的方案 type Events[X,Y] Array[(X, Y)] + [(Input, f1), (Change, f2)] 我觉得是更好的替代,同时 Array 方案也有额外的好处,就是可以Key可以多次重复,但视觉上可能没有map来得干净,同时属于破坏性更新
  4. 如果考虑兼容性,不做破坏性更新,我觉得还可以考虑以下这方案来针对你的这个项目:
    button(
      class_name=ui_button,
      inner_text="add",
      event={}..set(Click, on_submit),
    )
    
    /// 改成
    
    button(
      class_name=ui_button,
      inner_text="add",
    )..event(Click, on_submit)
    

嗯这个也可以试试. 不过最后估计还是看我的偏好了. 前面的版本因为是从 ClojureScript 最早实现的用了 hashmap, 也方便做 diff, 所以就沿用下来了. 我如果通过 Array[Tuple] 去初始化 hashmap, 使用的感觉差异不太大.

就是说这里的 .event(...) 方法返回的是 Element, 而不是 Unit 对吧. 这个主要的感觉是跟已有的用法差异比较大, 不倾向于这个.

整体确实也不符合直觉. 最初想的是找一个 2 的字面量语法, 没找到所以用了这个备用的方案. 但并不算是契合的.

索性改成了标签参数把 hashmap 藏到内部去,