ocaml2moonbit-migration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOCaml to MoonBit Migration
OCaml 到 MoonBit 迁移指南
Port behavior, data invariants, and public contracts first. Translate syntax only after the source semantics are classified. The most common porting bug is silently coercing OCaml (a byte sequence) into MoonBit (UTF-16 text); classify every field by meaning before choosing a type.
stringStringWhen in doubt, probe with . Probes in this guide were verified on -class toolchains; rerun the relevant probe if the local toolchain is newer and the behavior is load-bearing.
moon run -c '...'moon 0.1.20260512优先移植行为、数据不变量和公共契约。只有在明确源代码语义后,再进行语法转换。最常见的移植错误是将OCaml的(字节序列)隐式转换为MoonBit的(UTF-16文本);在选择类型前,务必根据含义对每个字段进行分类。
stringString如有疑问,使用进行探测。本指南中的探测示例均在版本系列的工具链上验证;若本地工具链更新且行为对业务至关重要,请重新运行相关探测。
moon run -c '...'moon 0.1.20260512Migration Workflow
迁移工作流
- Inventory the OCaml module boundary: public types, functions, exceptions, optional arguments, mutable state, lazy/deferred state, C/Unix/filesystem dependencies, and existing tests or golden fixtures.
- Classify every OCaml by meaning before choosing a MoonBit type. Do this field by field, even inside the same OCaml record.
string - Choose MoonBit package boundaries and imports before coding. Add imports to ; MoonBit source files do not use OCaml-style
moon.pkg.open - Port one behavioral slice at a time with tests. Prefer a thin public API skeleton, then fill parser/serializer/algorithm internals behind it.
- Probe uncertain language or library behavior with and, when needed, a small OCaml toplevel probe. Keep probes minimal.
moon run -c - Finish each slice with ,
moon check,moon test, andmoon info. You can add more warningsmoon fmtto be more strict.moon check --warn-list +...
- 梳理OCaml模块边界:公共类型、函数、异常、可选参数、可变状态、延迟/惰性状态、C/Unix/文件系统依赖,以及现有测试或基准用例。
- 在选择MoonBit类型前,根据含义对每个OCaml 进行分类。即使在同一个OCaml记录内,也要逐个字段进行分类。
string - 在编码前确定MoonBit包的边界和导入。将导入添加到中;MoonBit源文件不使用OCaml风格的
moon.pkg语句。open - 结合测试,一次移植一个行为切片。优先构建精简的公共API骨架,再填充其背后的解析器/序列化器/算法内部逻辑。
- 使用探测不确定的语言或库行为,必要时使用小型OCaml顶层探测。保持探测代码尽可能简洁。
moon run -c - 完成每个切片后,运行、
moon check、moon test和moon info。你可以添加更多警告选项moon fmt来提高严格性。moon check --warn-list +...
Type-Mapping Cheatsheet
类型映射速查表
| OCaml use | MoonBit default |
|---|---|
| binary payload, file contents, compressed/encrypted/checksummed data, parser input | |
| human-readable text, diagnostics, labels that are truly Unicode | |
| single byte with known 0..255 range | |
| indexes, counts, small identifiers, deliberate signed 32-bit wrapping | |
| file offsets, serialized positions, large object numbers | |
OCaml | |
| mutable growable builder | |
| read-only sequence parameter | |
| compile-time lookup table (literal or comprehension) | |
| keyed lookup with deterministic iteration | |
OCaml | |
OCaml | |
| OCaml variant | |
| OCaml record | |
| OCaml exception flow | |
| lazy/deferred state | |
OCaml | |
OCaml | |
OCaml | |
There is no type in the current toolchain.
Int32| OCaml 使用场景 | MoonBit 默认类型 |
|---|---|
| 二进制负载、文件内容、压缩/加密/带校验的数据、解析器输入 | |
| 人类可读文本、诊断信息、真正的Unicode标签 | |
| 已知范围为0..255的单个字节 | |
| 索引、计数、小型标识符、有意的32位有符号溢出 | |
| 文件偏移量、序列化位置、大型对象编号 | |
固定长度的OCaml | |
| 可变可增长的构建器 | |
| 只读序列参数 | |
| 编译时查找表(字面量或推导式) | |
| 具有确定性迭代的键值查找 | |
OCaml | |
OCaml | |
| OCaml 变体 | |
| OCaml 记录 | |
| OCaml 异常流程 | |
| 延迟/惰性状态 | |
需要溢出的OCaml | |
需要宽算术运算的OCaml | |
OCaml | |
当前工具链中没有类型。
Int32Bytes vs String
Bytes 与 String
OCaml counts bytes; MoonBit counts UTF-16 code units. The same source character takes different positions in the two languages, and that difference is silent.
String.lengthString::length()sh
ocaml -noprompt -noinit <<'EOF'
let s = "𝄞";;
Printf.printf "%d\n" (String.length s);;
Printf.printf "%d\n" (Char.code s.[0]);;
EOFOCaml的统计字节数;MoonBit的统计UTF-16代码单元数。同一个源字符在两种语言中占用的位置不同,且这种差异是隐式的。
String.lengthString::length()sh
ocaml -noprompt -noinit <<'EOF'
let s = "𝄞";;
Printf.printf "%d\n" (String.length s);;
Printf.printf "%d\n" (Char.code s.[0]);;
EOF4
4
240
240
```sh
moon run -c 'fn main { let s = "𝄞"; println(s.length()); println(s.char_length()) }'
```sh
moon run -c 'fn main { let s = "𝄞"; println(s.length()); println(s.char_length()) }'2
2
1
1
`String::length()` is UTF-16 code units, `String::char_length()` is Unicode scalars, and neither is bytes. For byte-oriented formats use `Bytes`/`BytesView`. Convert between `Bytes` and `String` only through a named encoding helper (`@ascii.encode`, `@utf8.encode`, etc.) that documents the encoding assumption.
```sh
moon run -c 'fn main { let raw : Array[Byte] = [65, 0, 255]; let b = Bytes::from_array(raw); println(b.length()); println(b[2].to_int()) }'
`String::length()`统计UTF-16代码单元数,`String::char_length()`统计Unicode标量数,两者都不是字节数。面向字节的格式请使用`Bytes`/`BytesView`。仅通过命名编码助手(`@ascii.encode`、`@utf8.encode`等)在`Bytes`和`String`之间转换,这些助手会记录编码假设。
```sh
moon run -c 'fn main { let raw : Array[Byte] = [65, 0, 255]; let b = Bytes::from_array(raw); println(b.length()); println(b[2].to_int()) }'3
3
255
255
```sh
moon run -c 'fn main { let source : Array[Byte] = [1, 2]; let bytes = Bytes::from_array(source); source[0] = 9; println(bytes[0].to_int()); println(source[0].to_int()) }'
```sh
moon run -c 'fn main { let source : Array[Byte] = [1, 2]; let bytes = Bytes::from_array(source); source[0] = 9; println(bytes[0].to_int()); println(source[0].to_int()) }'1
1
9
9
`Bytes::from_array` produces an immutable owned `Bytes`. Mutating the source array afterwards does not affect the frozen value. Port OCaml `Bytes` mutators either as `BytesView -> Bytes` transforms, or keep state in `Array[Byte]`/`FixedArray[Byte]` until the final freeze.
```sh
moon run -c 'fn main { let empty = Bytes::new(0); let zeros = Bytes::new(2); println(empty.length()); println(zeros.length()); println(zeros[0].to_int()) }'
`Bytes::from_array`生成不可变的自有`Bytes`。之后修改源数组不会影响已冻结的值。将OCaml `Bytes`的变异操作移植为`BytesView -> Bytes`转换,或者在最终冻结前将状态保存在`Array[Byte]`/`FixedArray[Byte]`中。
```sh
moon run -c 'fn main { let empty = Bytes::new(0); let zeros = Bytes::new(2); println(empty.length()); println(zeros.length()); println(zeros[0].to_int()) }'0
0
2
2
0
0
`Bytes::new(length)` always zero-fills and requires an explicit length. Use it where OCaml `Bytes.empty` or `Bytes.make len '\000'` would have appeared.
`Bytes::copy` is deprecated because `Bytes` is immutable. When a port must materialize a fresh physical copy (e.g. to preserve an OCaml promise that no buffer is shared), use `Bytes::makei(len, i => src[i])`.
```sh
moon run -c $'let cached_filter = b"/Filter"\nfn main { let b = b"PDF"; println(b.length()); println(b[0].to_int()); println(cached_filter.length()); println(cached_filter[0].to_int()) }'
`Bytes::new(length)`始终用零填充,且需要显式指定长度。在原本使用OCaml `Bytes.empty`或`Bytes.make len '\000'`的场景中使用它。
`Bytes::copy`已被弃用,因为`Bytes`是不可变的。当移植必须生成全新物理副本的代码时(例如为了保留OCaml中“不共享缓冲区”的承诺),请使用`Bytes::makei(len, i => src[i])`。
```sh
moon run -c $'let cached_filter = b"/Filter"\nfn main { let b = b"PDF"; println(b.length()); println(b[0].to_int()); println(cached_filter.length()); println(cached_filter[0].to_int()) }'3
3
80
80
7
7
47
47
`b"..."` is a compile-time byte-string literal of type `Bytes`. Use it for ASCII format syntax (`b"/Filter"`, `b"PDF"`, etc.) — no `@ascii` import, no runtime call, no type annotation needed at top level. Reserve `@ascii.encode(text)` for `String → Bytes` conversion when the source is a dynamic `String`. Do not build binary file formats by `String` concatenation.
```sh
moon run -c $'fn main { let buf = @buffer.new(); buf.write_bytes(b"PDF"); buf.write_byte(10); let bytes = buf.contents(); println(bytes.length()); println(bytes[0].to_int()); println(bytes[3].to_int()) }'
`b"..."`是编译时字节字符串字面量,类型为`Bytes`。将其用于ASCII格式语法(如`b"/Filter"`、`b"PDF"`等)——无需导入`@ascii`,无需运行时调用,顶层无需类型注解。当源是动态`String`时,保留`@ascii.encode(text)`用于`String → Bytes`转换。不要通过`String`拼接来构建二进制文件格式。
```sh
moon run -c $'fn main { let buf = @buffer.new(); buf.write_bytes(b"PDF"); buf.write_byte(10); let bytes = buf.contents(); println(bytes.length()); println(bytes[0].to_int()); println(bytes[3].to_int()) }'4
4
80
80
10
10
The canonical MoonBit byte-builder is `@buffer.Buffer` (from `moonbitlang/core/buffer`). Construct with `@buffer.new()` (optionally `size_hint=N`), append static ASCII via `buf.write_bytes(b"...")`, single bytes via `buf.write_byte(n)`, dynamic text via `buf.write_bytes(@ascii.encode(text))`, binary payloads via `buf.write_bytes(view)`, then freeze with `buf.contents()`. Reserve `Array[Byte]` plus `Bytes::from_array` for cases that need random-access mutation of in-flight bytes; `Buffer` is the OCaml `Buffer.t` analogue.
```sh
moon run -c 'fn main { let s = "/UniJIS-UCS2-H"; println(s.has_prefix("/Uni")); println(s.contains("-UCS2-")); println(s.has_suffix("-H")); println(s[1:]); println(s[1:].to_owned()) }'
标准的MoonBit字节构建器是`@buffer.Buffer`(来自`moonbitlang/core/buffer`)。使用`@buffer.new()`(可选指定`size_hint=N`)构建,通过`buf.write_bytes(b"...")`追加静态ASCII,通过`buf.write_byte(n)`追加单个字节,通过`buf.write_bytes(@ascii.encode(text))`追加动态文本,通过`buf.write_bytes(view)`追加二进制负载,然后使用`buf.contents()`冻结。仅在需要随机访问修改流转字节的场景中使用`Array[Byte]`加`Bytes::from_array`;`Buffer`是OCaml `Buffer.t`的对应物。
```sh
moon run -c 'fn main { let s = "/UniJIS-UCS2-H"; println(s.has_prefix("/Uni")); println(s.contains("-UCS2-")); println(s.has_suffix("-H")); println(s[1:]); println(s[1:].to_owned()) }'true
true
true
true
true
true
UniJIS-UCS2-H
UniJIS-UCS2-H
UniJIS-UCS2-H
UniJIS-UCS2-H
`String::has_prefix`, `has_suffix`, and `contains` cover predicate work. Slice with `s[start:end]` — like `BytesView`, this returns a borrowed `StringView`, cheap and good for inspection/pattern matching. When the callee needs an owned `String` (e.g. for storage or a `String` parameter), use `s[start:end].to_owned()`. Offsets are UTF-16 code-unit offsets, not bytes; use these only for ASCII-validated tokens or genuinely textual parsing.
`String::has_prefix`、`has_suffix`和`contains`用于谓词操作。使用`s[start:end]`进行切片——与`BytesView`类似,这会返回一个借用的`StringView`,成本低廉,适合检查/模式匹配。当调用方需要自有`String`时(例如用于存储或作为`String`参数),使用`s[start:end].to_owned()`。偏移量是UTF-16代码单元偏移量,而非字节数;仅在ASCII验证过的令牌或真正的文本解析场景中使用这些方法。Bytes Views
Bytes 视图
sh
moon run -c 'fn has_ab(view : BytesView) -> Bool { match view { [65, 66, ..] => true; _ => false } }
fn main { let bytes : Bytes = [65, 66, 67]; let view = bytes[:2]; println(view.length()); println(view[0].to_int()); println(has_ab(bytes[:])); println(has_ab(view)) }'sh
moon run -c 'fn has_ab(view : BytesView) -> Bool { match view { [65, 66, ..] => true; _ => false } }
fn main { let bytes : Bytes = [65, 66, 67]; let view = bytes[:2]; println(view.length()); println(view[0].to_int()); println(has_ab(bytes[:])); println(has_ab(view)) }'2
2
65
65
true
true
true
true
`BytesView` is the byte-sequence counterpart to `ArrayView`. Views are cheap slices, expose read-only byte operations, and support pattern matching with rest patterns. Prefer `BytesView` for read-only byte APIs; call `.to_owned()` only at explicit ownership boundaries. `BytesView::to_owned()` returns the original bytes for a whole view but allocates and copies for a partial slice.
`Bytes` is assignable to a `BytesView` parameter, so a single API can accept owned or borrowed input. A returned `BytesView` keeps its backing `Bytes` alive across function boundaries — useful for exposing decode/decrypt results without forcing a copy.
The reverse direction is **not** automatic: a `BytesView` does not type-check where owned `Bytes` is required, including in native extern declarations. Treat that as the ownership boundary and call `.to_owned()` deliberately. Equality works when the view is the left operand; if `Bytes` is on the left, slice it (`bytes[:]`) before comparing.
```sh
moon run -c 'fn main { let data : Bytes = [60, 65, 62, 0, 12, 60, 66, 62]; println(data.length()); println(data[3].to_int()); println(data[4].to_int()); let view = data[5:]; match view { [60, 66, 62] => println("match"); _ => println("miss") } }'
`BytesView`是字节序列的对应物,类似于`ArrayView`。视图是低成本的切片,提供只读字节操作,并支持带剩余模式的模式匹配。只读字节API优先使用`BytesView`;仅在显式所有权边界处调用`.to_owned()`。`BytesView::to_owned()`对完整视图返回原始字节,但对部分切片会分配并复制。
`Bytes`可赋值给`BytesView`参数,因此单个API可以接受自有或借用的输入。返回的`BytesView`会在函数边界之间保持其底层`Bytes`存活——这对于暴露解码/解密结果而不强制复制非常有用。
反向转换**不**自动进行:`BytesView`在需要自有`Bytes`的位置(包括原生外部声明)无法通过类型检查。将其视为所有权边界,并刻意调用`.to_owned()`。当视图是左操作数时,相等性检查有效;如果`Bytes`在左侧,请先切片(`bytes[:]`)再比较。
```sh
moon run -c 'fn main { let data : Bytes = [60, 65, 62, 0, 12, 60, 66, 62]; println(data.length()); println(data[3].to_int()); println(data[4].to_int()); let view = data[5:]; match view { [60, 66, 62] => println("match"); _ => println("miss") } }'8
8
0
0
12
12
match
match
NUL (`0`) and form-feed (`12`) are ordinary bytes in `Bytes`/`BytesView`. Port OCaml byte predicates literally; do not narrow a format whitespace predicate to a string-oriented space/tab check.
空字符(`0`)和换页符(`12`)在`Bytes`/`BytesView`中是普通字节。按字面移植OCaml字节谓词;不要将格式空白谓词缩小为面向字符串的空格/制表符检查。Integers
整数
sh
moon run -c 'fn main { let (b, u, u16, i64, u64) : (Byte, UInt, UInt16, Int64, UInt64) = (255, 7, 65535, 42, 42); println(b.to_int()); println(u.to_string()); println(u16.to_int()); println(i64.to_string()); println(u64.to_string()) }'sh
moon run -c 'fn main { let (b, u, u16, i64, u64) : (Byte, UInt, UInt16, Int64, UInt64) = (255, 7, 65535, 42, 42); println(b.to_int()); println(u.to_string()); println(u16.to_int()); println(i64.to_string()); println(u64.to_string()) }'255
255
7
7
65535
65535
42
42
42
42
Scalar types: `Byte`, `Int16`, `UInt16`, `Int`, `UInt`, `Int64`, `UInt64`, `Float`, `Double`. There is **no** `Int32`; use `Int` for deliberate signed 32-bit wrapping, or `Int64`/`UInt64` when an OCaml `int32` value must be represented without truncation. When calling a method on an integer literal, parenthesize: `(65).to_byte()`, not `65.to_byte()` — `65.` parses as the start of a float.
```sh
moon run -c 'fn main { let max = 2147483647; println(max + 1); println(1 << 31); println(0xF0 & 0x0F); println(0x80 >> 7); println(0xAA ^ 0xFF) }'
标量类型:`Byte`、`Int16`、`UInt16`、`Int`、`UInt`、`Int64`、`UInt64`、`Float`、`Double`。**没有**`Int32`类型;有意进行32位有符号溢出时使用`Int`,或者当OCaml `int32`值必须无截断表示时使用`Int64`/`UInt64`。对整数字面量调用方法时,需要加括号:`(65).to_byte()`,而非`65.to_byte()`——`65.`会被解析为浮点数的开头。
```sh
moon run -c 'fn main { let max = 2147483647; println(max + 1); println(1 << 31); println(0xF0 & 0x0F); println(0x80 >> 7); println(0xAA ^ 0xFF) }'-2147483648
-2147483648
-2147483648
-2147483648
0
0
1
1
85
85
`Int` arithmetic wraps modulo 2^32 with signed interpretation. Symbolic operators map directly from OCaml: `&` (land), `|` (lor), `^` (lxor), `<<` (lsl), `>>` (right shift, arithmetic for signed).
```sh
moon run -c 'fn main { println((-8) >> 1); println(1 << 32); println(1 >> 32); let logical = ((-8).reinterpret_as_uint() >> 1).reinterpret_as_int(); println(logical) }'
`Int`运算以2^32为模进行有符号溢出。符号运算符直接映射自OCaml:`&`(land)、`|`(lor)、`^`(lxor)、`<<`(lsl)、`>>`(右移,有符号数为算术右移)。
```sh
moon run -c 'fn main { println((-8) >> 1); println(1 << 32); println(1 >> 32); let logical = ((-8).reinterpret_as_uint() >> 1).reinterpret_as_int(); println(logical) }'-4
-4
1
1
1
1
2147483644
2147483644
`Int` right shift is arithmetic, and shift counts are **masked to 5 bits**, so `1 << 32 == 1`. For OCaml `Int32.shift_right_logical`, reinterpret signed→unsigned, shift, reinterpret back. Numeric conversion (`.to_uint()`) is **not** the same as bit reinterpretation (`.reinterpret_as_uint()`).
```sh
moon run -c $'fn main { let parsed : Result[Int, Error] = try? @string.parse_int("2147483648"); println(parsed is Err(_)); let mut value = 0; for digit in [50,49,52,55,52,56,51,54,52,56] { value = value * 10 + digit - 48 }; println(value); let mut wide = 0L; for digit in [50,49,52,55,52,56,51,54,52,56] { wide = wide * 10L + (digit - 48).to_int64() }; println(wide.to_string()) }'
`Int`右移是算术右移,且移位计数会**掩码为5位**,因此`1 << 32 == 1`。对于OCaml的`Int32.shift_right_logical`,先将有符号数重新解释为无符号数,移位后再重新解释回有符号数。数值转换(`.to_uint()`)**不等同于**位重新解释(`.reinterpret_as_uint()`)。
```sh
moon run -c $'fn main { let parsed : Result[Int, Error] = try? @string.parse_int("2147483648"); println(parsed is Err(_)); let mut value = 0; for digit in [50,49,52,55,52,56,51,54,52,56] { value = value * 10 + digit - 48 }; println(value); let mut wide = 0L; for digit in [50,49,52,55,52,56,51,54,52,56] { wide = wide * 10L + (digit - 48).to_int64() }; println(wide.to_string()) }'true
true
-2147483648
-2147483648
2147483648
2147483648
`@string.parse_int` rejects out-of-range decimals, but handwritten digit accumulation in `Int` silently wraps. Accumulate offsets, lengths, object numbers, or serialized counters in `Int64`/`UInt64`; bounds-check before narrowing to `Int`.
```sh
moon run -c 'fn main { println(0x8EA2A1A1 < 0); println(0x8EA2A1A1); println(0x7FFFFFFF < 0x8EA2A1A1); println((-1) % 256) }'
`@string.parse_int`会拒绝超出范围的十进制数,但`Int`中的手写数字累加会静默溢出。偏移量、长度、对象编号或序列化计数器使用`Int64`/`UInt64`累加;缩小为`Int`前进行边界检查。
```sh
moon run -c 'fn main { println(0x8EA2A1A1 < 0); println(0x8EA2A1A1); println(0x7FFFFFFF < 0x8EA2A1A1); println((-1) % 256) }'true
true
-1901944415
-1901944415
false
false
-1
-1
`Int` literals above `0x7FFFFFFF` are negative. A table sorted by unsigned byte order is **not** sorted for signed `Int` comparison — compare through `UInt` (`reinterpret_as_uint`) or store as `Int64`/`UInt64`. `%` preserves the sign of the left operand; normalize `(a - b) mod 256` style expressions into `0..255` before converting to `Byte`. For byte-codec arithmetic that multiplies by large radices, promote `Byte` to `UInt64`; `UInt64::to_int()` truncates to signed 32 bits for large values.
```sh
moon run -c 'fn rotr64(value : UInt64, bits : Int) -> UInt64 { (value >> bits) | (value << (64 - bits)) }
fn main { let value : UInt64 = 0x0123456789abcdef; let full : UInt64 = UInt64::lnot(0); println(rotr64(value, 8).to_string()); println((full + (1 : UInt64)).to_string()); println(((0x80 : UInt64) >> 7).to_string()) }'
大于`0x7FFFFFFF`的`Int`字面量为负数。按无符号字节顺序排序的表**不会**按有符号`Int`比较排序——通过`UInt`(`reinterpret_as_uint`)进行比较,或存储为`Int64`/`UInt64`。`%`保留左操作数的符号;将`(a - b) mod 256`风格的表达式归一化到`0..255`范围后再转换为`Byte`。对于乘以大基数的字节编解码器运算,将`Byte`提升为`UInt64`;`UInt64::to_int()`会将大值截断为32位有符号数。
```sh
moon run -c 'fn rotr64(value : UInt64, bits : Int) -> UInt64 { (value >> bits) | (value << (64 - bits)) }
fn main { let value : UInt64 = 0x0123456789abcdef; let full : UInt64 = UInt64::lnot(0); println(rotr64(value, 8).to_string()); println((full + (1 : UInt64)).to_string()); println(((0x80 : UInt64) >> 7).to_string()) }'17222085231038278605
17222085231038278605
0
0
1
1
`UInt64` arithmetic wraps modulo 2^64. Unsigned right shifts are logical. Provide explicit `UInt64` context (`x : UInt64`) when the literal exceeds signed `Int` range.
```sh
moon run -c 'fn main { println(Int::lnot(0)); println((0x0F).lnot()); println((!true).to_string()) }'
`UInt64`运算以2^64为模进行无符号溢出。无符号右移是逻辑右移。当字面量超出有符号`Int`范围时,提供显式的`UInt64`上下文(`x : UInt64`)。
```sh
moon run -c 'fn main { println(Int::lnot(0)); println((0x0F).lnot()); println((!true).to_string()) }'-1
-1
-16
-16
false
false
Bitwise complement: `Int::lnot(value)` or `value.lnot()`. Boolean negation: `!expr`. Do not port OCaml `not x` literally.
位取反:`Int::lnot(value)`或`value.lnot()`。布尔取反:`!expr`。不要按字面移植OCaml的`not x`。Floats
浮点数
sh
ocaml -noprompt -noinit <<'EOF'
print_endline (string_of_float 2.);;
print_endline (string_of_float infinity);;
EOFsh
ocaml -noprompt -noinit <<'EOF'
print_endline (string_of_float 2.);;
print_endline (string_of_float infinity);;
EOF2.
2.
inf
inf
moon run -c 'fn main raise { println((2.0).to_string()); println((1.0 / 0.0).to_string()); let tiny : Double = @string.from_str("1e-10"); println(tiny.to_string()) }'
moon run -c 'fn main raise { println((2.0).to_string()); println((1.0 / 0.0).to_string()); let tiny : Double = @string.from_str("1e-10"); println(tiny.to_string()) }'
2
2
Infinity
Infinity
1e-10
1e-10
`Double::to_string()` is **not** a drop-in for OCaml `string_of_float` or `Printf "%.12g"`. Whole floats render as `2` (not `2.`); infinities render as `Infinity`/`-Infinity` (not `inf`/`-inf`); small values use scientific notation. When a digest, file format, or snapshot depends on the exact spelling, port the OCaml formatter as an explicit boundary function. Overflow produces `Infinity`; division by infinity produces `0`. Cover those edges with a probe when porting defensive `float` branches.
```sh
moon run -c 'fn main { println(@math.pow(0.5, 2.0)); println((4.0).sqrt()); println((-3.0).abs()); println((5.5).mod(2.0)) }'
`Double::to_string()`**不能**直接替代OCaml的`string_of_float`或`Printf "%.12g"`。整数浮点数显示为`2`(而非`2.`);无穷大显示为`Infinity`/`-Infinity`(而非`inf`/`-inf`);小值使用科学计数法。当摘要、文件格式或快照依赖于精确的字符串表示时,将OCaml格式化器移植为显式的边界函数。溢出会产生`Infinity`;除以无穷大会产生`0`。移植防御性`float`分支时,用探测覆盖这些边缘情况。
```sh
moon run -c 'fn main { println(@math.pow(0.5, 2.0)); println((4.0).sqrt()); println((-3.0).abs()); println((5.5).mod(2.0)) }'0.25
0.25
2
2
3
3
1.5
1.5
There is no `**` operator — use `@math.pow(base, exponent)`. Common helpers are `Double` methods: `value.sqrt()`, `value.abs()`, `value.mod(other)`. Free functions live in `@math`: `sin`, `cos`, `atan2`, `ln`, `log10`, `floor`, `ceil`, `round`, `trunc`, `exp`. Note `@math.sqrt`/`@math.abs` are **not** valid — those are method-only. Probe with `moon ide doc @math` before assuming a path.
没有`**`运算符——使用`@math.pow(base, exponent)`。常用辅助函数是`Double`的方法:`value.sqrt()`、`value.abs()`、`value.mod(other)`。自由函数位于`@math`中:`sin`、`cos`、`atan2`、`ln`、`log10`、`floor`、`ceil`、`round`、`trunc`、`exp`。注意`@math.sqrt`/`@math.abs`是**无效的**——这些只能作为方法调用。在假设路径前,使用`moon ide doc @math`进行探测。Data Structures
数据结构
Map OCaml variants to MoonBit . Derive , for types that will be inspected in tests.
enumDebugEqsh
moon run -c 'enum E { A(Int) } derive(Debug)
fn E::value(self : E) -> Int { match self { A(n) => n } }
fn main { let value = A(7); println(value.value()) }'将OCaml变体映射到MoonBit的。为需要在测试中检查的类型派生、。
enumDebugEqsh
moon run -c 'enum E { A(Int) } derive(Debug)
fn E::value(self : E) -> Int { match self { A(n) => n } }
fn main { let value = A(7); println(value.value()) }'7
7
```sh
moon run -c 'enum E { A(Int); B(Int) } derive(Debug)
fn main { let xs : Array[E] = [E::A(1), E::B(2)]; println(xs.length()) }'
```sh
moon run -c 'enum E { A(Int); B(Int) } derive(Debug)
fn main { let xs : Array[E] = [E::A(1), E::B(2)]; println(xs.length()) }'2
2
When code crosses package boundaries or builds heterogeneous-looking enum arrays, prefer explicit `Type::Constructor` forms or a concrete element-type annotation. This keeps `moon check --warn-list +73` useful — strip annotations only when the compiler reports them as truly unnecessary.
```sh
moon run -c 'priv enum Section { NoneSection; ActiveSection }
fn choose(flag : Bool) -> Section { if flag { ActiveSection } else { NoneSection } }
fn main { match choose(true) { ActiveSection => println("active"); NoneSection => println("none") } }'
当代码跨包边界或构建异构枚举数组时,优先使用显式的`Type::Constructor`形式或具体的元素类型注解。这能让`moon check --warn-list +73`发挥作用——仅当编译器报告注解确实不必要时,再移除注解。
```sh
moon run -c 'priv enum Section { NoneSection; ActiveSection }
fn choose(flag : Bool) -> Section { if flag { ActiveSection } else { NoneSection } }
fn main { match choose(true) { ActiveSection => println("active"); NoneSection => println("none") } }'active
active
```sh
moon run -c 'priv struct S { value : Int }
fn make() -> S { { value: 1 } }
fn main { let s = make(); println(s.value) }'
```sh
moon run -c 'priv struct S { value : Int }
fn make() -> S { { value: 1 } }
fn main { let s = make(); println(s.value) }'1
1
Use `priv enum`/`priv struct` for internal helpers that should not appear in the package interface. Avoid deriving traits on private helpers unless a debug path actually uses the implementation.
```sh
moon run -c 'struct S { a : Int; b : Int? } derive(Debug, Eq)
fn main { let s = { a: 1, b: None }; let t = { ..s, b: Some(2) }; match t.b { Some(value) => println(value); None => println(0) } }'
对不应出现在包接口中的内部辅助类型,使用`priv enum`/`priv struct`。除非调试路径实际使用实现,否则避免为私有辅助类型派生 trait。
```sh
moon run -c 'struct S { a : Int; b : Int? } derive(Debug, Eq)
fn main { let s = { a: 1, b: None }; let t = { ..s, b: Some(2) }; match t.b { Some(value) => println(value); None => println(0) } }'2
2
Record update is `{ ..old, field: value }`, the direct replacement for OCaml `{ old with field = value }`.
```sh
moon run -c 'struct S { values : Int } derive(Debug)
fn make(values : Int) -> S? { Some({ values, }) }
fn main { println(make(7).unwrap().values) }'
记录更新使用`{ ..old, field: value }`,是OCaml `{ old with field = value }`的直接替代。
```sh
moon run -c 'struct S { values : Int } derive(Debug)
fn make(values : Int) -> S? { Some({ values, }) }
fn main { println(make(7).unwrap().values) }'7
7
For single-field struct literals using field shorthand inside another expression, write a trailing comma (`{ values, }`) to silence the ambiguous-block warning.
When an OCaml function returns a broad variant and later code relies on an informal invariant, introduce a narrow private MoonBit enum for the post-checked state. This makes impossible fallback branches explicit at the type boundary rather than carrying broad `Object`-like values through the rest of the port.
在另一个表达式中使用字段简写的单字段记录字面量时,添加尾随逗号(`{ values, }`)以消除歧义块警告。
当OCaml函数返回宽泛的变体,且后续代码依赖于非正式不变量时,为检查后的状态引入一个窄范围的私有MoonBit枚举。这会在类型边界处显式标记不可能的回退分支,而非在移植的其余部分中携带类似`Object`的宽泛值。Lookup Tables
查找表
sh
moon run -c 'fn range_lookup(ranges : ArrayView[(Int, Int, Int)], key : Int) -> Int? { for range in ranges { if key >= range.0 && key <= range.1 { return Some(range.2 + key - range.0) } }; None }
fn main { let ranges = [(0x21, 0x23, 100), (0x30, 0x30, 200)]; println(range_lookup(ranges, 0x22).unwrap()); println(range_lookup(ranges, 0x24) is None) }'sh
moon run -c 'fn range_lookup(ranges : ArrayView[(Int, Int, Int)], key : Int) -> Int? { for range in ranges { if key >= range.0 && key <= range.1 { return Some(range.2 + key - range.0) } }; None }
fn main { let ranges = [(0x21, 0x23, 100), (0x30, 0x30, 200)]; println(range_lookup(ranges, 0x22).unwrap()); println(range_lookup(ranges, 0x24) is None) }'101
101
true
true
For large OCaml mapping tables, prefer compact `ArrayView`-accepted range tables (`(first, last, base)` plus small exception/sequence tables) over mechanically expanding every entry. Generated MoonBit sources stay smaller and native-target validation runs faster, with the same deterministic lookup behavior.
```sh
moon run -c $'let table : ReadOnlyArray[Int?] = [for i in 0..<8 => if i == 5 { Some(10) } else { None }]\nfn lookup(i : Int) -> Int? { if i >= 0 && i < table.length() { table[i] } else { None } }\nfn main { println(lookup(5).unwrap()); println(lookup(0) == None); println(lookup(99) == None); println(table.length()) }'
对于大型OCaml映射表,优先使用紧凑的`ArrayView`接受的范围表(`(first, last, base)`加上小型异常/序列表),而非机械地展开每个条目。生成的MoonBit源文件更小,原生目标验证运行更快,且具有相同的确定性查找行为。
```sh
moon run -c $'let table : ReadOnlyArray[Int?] = [for i in 0..<8 => if i == 5 { Some(10) } else { None }]\nfn lookup(i : Int) -> Int? { if i >= 0 && i < table.length() { table[i] } else { None } }\nfn main { println(lookup(5).unwrap()); println(lookup(0) == None); println(lookup(99) == None); println(table.length()) }'10
10
true
true
true
true
8
8
For dense lookup domains, define a private top-level `let table : ReadOnlyArray[T?] = [...]`. `ReadOnlyArray[T]` is the read-only counterpart of `Array[T]`; an array literal coerces directly, and **list comprehensions also produce `ReadOnlyArray[T]`** — so `[for i in 0..<N => entry(i)]` is the idiomatic way to populate a compile-time table without `makei` ceremony. Keep the bounds check at the lookup boundary.
对于密集查找域,定义一个私有顶层`let table : ReadOnlyArray[T?] = [...]`。`ReadOnlyArray[T]`是`Array[T]`的只读对应物;数组字面量可直接强制转换,且**列表推导式也会生成`ReadOnlyArray[T]`**——因此`[for i in 0..<N => entry(i)]`是填充编译时表的惯用方式,无需`makei`仪式。在查找边界处保留边界检查。Mutation, Refs, Arrays
变异、引用、数组
sh
moon run -c $'fn first_sorted(xs : ArrayView[Int]) -> Int { let ys = xs.to_owned(); ys.sort(); ys[0] }\nfn main { let r : Ref[Int] = Ref::{ val: 0 }; r.val += 1; println(r.val); let fixed : FixedArray[Int] = [3, 1, 2]; let grow = [4, 2, 3]; println(first_sorted(fixed)); println(first_sorted(grow)); println(fixed[0]); println(grow[0]) }'sh
moon run -c $'fn first_sorted(xs : ArrayView[Int]) -> Int { let ys = xs.to_owned(); ys.sort(); ys[0] }\nfn main { let r : Ref[Int] = Ref::{ val: 0 }; r.val += 1; println(r.val); let fixed : FixedArray[Int] = [3, 1, 2]; let grow = [4, 2, 3]; println(first_sorted(fixed)); println(first_sorted(grow)); println(fixed[0]); println(grow[0]) }'1
1
1
1
2
2
3
3
4
4
`Ref[T]` for primitive mutability; `mut` fields inside structs for larger state. `Array` is a growable mutable vector; `FixedArray` matches OCaml `array` more directly. `ArrayView[T]` is the read-only sequence parameter type — callers can pass `Array`, `FixedArray`, or another view without copying. `.to_owned()` materializes a mutable `Array` at explicit ownership boundaries (e.g. before `sort()`/`sort_by()`); avoid it in hot paths. Represent lazy/deferred states as explicit `enum` variants instead of nested mutable containers.
```sh
moon run -c 'fn main { let xs = [(2, "b"), (1, "a")]; xs.sort_by(fn(a, b) { a.0.compare(b.0) }); println(xs[0].0); let ys = [1, 2, 3]; let zs = ys.rev(); println(zs[0]); println(ys[0]) }'
`Ref[T]`用于基本类型的可变性;结构体中的`mut`字段用于更大的状态。`Array`是可增长的可变向量;`FixedArray`更接近OCaml的`array`。`ArrayView[T]`是只读序列参数类型——调用方无需复制即可传递`Array`、`FixedArray`或其他视图。`.to_owned()`在显式所有权边界处生成可变的`Array`(例如在`sort()`/`sort_by()`之前);在热路径中避免使用它。将延迟/惰性状态表示为显式的`enum`变体,而非嵌套的可变容器。
```sh
moon run -c 'fn main { let xs = [(2, "b"), (1, "a")]; xs.sort_by(fn(a, b) { a.0.compare(b.0) }); println(xs[0].0); let ys = [1, 2, 3]; let zs = ys.rev(); println(zs[0]); println(ys[0]) }'1
1
3
3
1
1
`Array::sort_by` sorts in place with an `Int`-returning comparator. `Array::rev()` returns a reversed **copy** and leaves the original unchanged — useful when porting OCaml list-building code that conses in reverse and conditionally applies `List.rev`.
```sh
moon run -c $'fn main { let m : Map[Int, String] = Map([]); m[3] = "three"; m[1] = "one"; m[2] = "two"; for k, v in m { println("\\{k}=\\{v}") } }'
`Array::sort_by`使用返回`Int`的比较器进行原地排序。`Array::rev()`返回反转的**副本**,且不修改原数组——这在移植OCaml列表构建代码时很有用,此类代码通常反向cons,并有条件地应用`List.rev`。
```sh
moon run -c $'fn main { let m : Map[Int, String] = Map([]); m[3] = "three"; m[1] = "one"; m[2] = "two"; for k, v in m { println("\\{k}=\\{v}") } }'3=three
3=three
1=one
1=one
2=two
2=two
For keyed lookup, prefer the built-in `Map[K, V]` — it is a **linked hashmap that preserves insertion order**, so iteration is deterministic and matches the order keys were written. Construct with `Map([])` (or `Map([], capacity=N)`); `Map::new()` is deprecated. Reach for `@hashmap.HashMap[K, V]` (from `moonbitlang/core/hashmap`) only when insertion order is irrelevant and you want a marginally cheaper structure. Use ordered arrays of pairs only when you need duplicate keys or sequence-style processing.
对于键值查找,优先使用内置的`Map[K, V]`——它是**保留插入顺序的链接哈希表**,因此迭代是确定性的,与键的写入顺序一致。使用`Map([])`(或`Map([], capacity=N)`)构建;`Map::new()`已被弃用。仅当插入顺序无关紧要且需要成本略低的结构时,才使用`@hashmap.HashMap[K, V]`(来自`moonbitlang/core/hashmap`)。仅当需要重复键或序列式处理时,才使用有序的键值对数组。Errors
错误
OCaml exceptions (, , , domain errors) should not be copied as unchecked control flow.
Not_foundEnd_of_fileInvalid_argumentsh
moon run -c $'suberror E\nfn make(flag : Bool) -> ((Int) -> Int?) raise E { if flag { raise E } else { fn(x) { Some(x) } } }\nfn main raise { println(make(false)(3).unwrap()) }'OCaml异常(、、、域错误)不应作为未检查的控制流复制。
Not_foundEnd_of_fileInvalid_argumentsh
moon run -c $'suberror E\nfn make(flag : Bool) -> ((Int) -> Int?) raise E { if flag { raise E } else { fn(x) { Some(x) } } }\nfn main raise { println(make(false)(3).unwrap()) }'3
3
```sh
moon run -c $'fn parse(s : String) -> Int raise { @string.parse_int(s) }\nfn helper() -> Int raise { parse("7") }\ntest "raising test body can propagate" { let value = parse("7"); @test.assert_eq(value, 7) }\nfn main raise { println(helper().to_string()) }'
```sh
moon run -c $'fn parse(s : String) -> Int raise { @string.parse_int(s) }\nfn helper() -> Int raise { parse("7") }\ntest "raising test body can propagate" { let value = parse("7"); @test.assert_eq(value, 7) }\nfn main raise { println(helper().to_string()) }'7
7
Define a project-level `suberror` once the first fallible functions are ported. Fallible functions declare `raise ProjectError` or plain `raise`. **`main` itself can be declared `fn main raise { ... }`**, which is the cleanest way to write probes and small entry points that call fallible APIs — no `try!` boilerplate. Inside a raising function, raising test helper, or `test` body, do not add explicit `try!` around every fallible call; checked errors propagate from those contexts automatically. Test helpers that can call `fail` must declare `raise`, even when only called from `test` blocks.
When a raising function returns another function, parenthesize the function type before `raise`: `-> ((A) -> B) raise E`, **not** `-> (A) -> B raise E` (the latter binds `raise` to the returned function type).
```sh
moon run -c $'fn may_fail(flag : Bool) -> Unit raise { if flag { fail("boom") } }\nfn main { let result : Result[Unit, Error] = try? may_fail(true); println((result is Err(_)).to_string()) }'
在移植第一个易出错函数后,定义一个项目级的`suberror`。易出错函数声明`raise ProjectError`或仅`raise`。**`main`本身可以声明为`fn main raise { ... }`**,这是编写探测和调用易出错API的小型入口点最简洁的方式——无需`try!`样板代码。在抛出函数、抛出测试辅助函数或`test`体内,无需在每个易出错调用周围添加显式的`try!`;受检错误会自动从这些上下文中传播。可以调用`fail`的测试辅助函数必须声明`raise`,即使仅从`test`块调用。
当抛出函数返回另一个函数时,在`raise`之前为函数类型添加括号:`-> ((A) -> B) raise E`,**不要**写成`-> (A) -> B raise E`(后者会将`raise`绑定到返回的函数类型)。
```sh
moon run -c $'fn may_fail(flag : Bool) -> Unit raise { if flag { fail("boom") } }\nfn main { let result : Result[Unit, Error] = try? may_fail(true); println((result is Err(_)).to_string()) }'true
true
```sh
moon run -c $'fn parse(s : String) -> Int raise { @string.parse_int(s) }\nfn main {\n try parse("7") catch {\n _ => println(0)\n } noraise {\n value => println(value)\n }\n}'
```sh
moon run -c $'fn parse(s : String) -> Int raise { @string.parse_int(s) }\nfn main {\n try parse("7") catch {\n _ => println(0)\n } noraise {\n value => println(value)\n }\n}'7
7
`try? expr` materializes failure as `Result[T, Error]` — convenient in tests, but loses the narrower suberror type for re-raising. `try expr catch { ... } noraise { value => ... }` is the OCaml `try ... with ...` analogue with explicit success and error branches.
To catch one constructor and propagate the rest while keeping the project error type, use `expr catch { SpecificError => fallback; error => raise error }`. `catch` arms are checked for exhaustiveness; do not omit the final propagating arm. When porting OCaml exception tests, default to `Err(_)`; match a specific variant only when that variant is itself the contract under test.
Avoid `match (try? f()) { Ok(...) => ...; Err(...) => ... }` — MoonBit warns on it. Prefer `try f() catch { ... } noraise { ... }`. If you must match a `try?` result, wrap it in parentheses; `match try? expr { ... }` is invalid syntax.
`try? expr`将失败转换为`Result[T, Error]`——在测试中很方便,但会丢失用于重新抛出的更窄的子错误类型。`try expr catch { ... } noraise { value => ... }`是OCaml `try ... with ...`的对应物,具有显式的成功和错误分支。
要捕获一个构造函数并传播其余错误,同时保留项目错误类型,请使用`expr catch { SpecificError => fallback; error => raise error }`。`catch`分支会被检查是否穷尽;不要省略最终的传播分支。移植OCaml异常测试时,默认使用`Err(_)`;仅当特定变体本身是测试的契约时,才匹配该变体。
避免使用`match (try? f()) { Ok(...) => ...; Err(...) => ... }`——MoonBit会对此发出警告。优先使用`try f() catch { ... } noraise { ... }`。如果必须匹配`try?`的结果,请将其包裹在括号中;`match try? expr { ... }`是无效语法。Functions and Callbacks
函数与回调
sh
moon run -c 'fn helper(x : Int) -> Int { x + 1 }
fn main { println(helper(1)) }'sh
moon run -c 'fn helper(x : Int) -> Int { x + 1 }
fn main { println(helper(1)) }'2
2
Top-level helper functions (anything other than `main`) need explicit return-type annotations. This applies to `moon run -c` probes too.
```sh
moon run -c 'fn[A, B, C] apply_pair(f : (A, B) -> C, a : A, b : B) -> (C, A) { (f(a, b), a) }
fn main { let result = apply_pair(fn(x, y) { x + y }, 2, 3); println(result.0); println(result.1) }'
顶层辅助函数(除`main`外的任何函数)需要显式的返回类型注解。这也适用于`moon run -c`探测。
```sh
moon run -c 'fn[A, B, C] apply_pair(f : (A, B) -> C, a : A, b : B) -> (C, A) { (f(a, b), a) }
fn main { let result = apply_pair(fn(x, y) { x + y }, 2, 3); println(result.0); println(result.1) }'5
5
2
2
Polymorphic functions write type parameters **before** the name: `fn[A, B, C] name(...)`. The older `fn name[A, B, C](...)` spelling is deprecated.
```sh
moon run -c 'fn apply_twice(f : (Int) -> Int, value : Int) -> Int { f(f(value)) }
fn main { println(apply_twice(fn(x) { x + 1 }, 40)) }'
多态函数在名称**之前**编写类型参数:`fn[A, B, C] name(...)`。旧的`fn name[A, B, C](...)`写法已被弃用。
```sh
moon run -c 'fn apply_twice(f : (Int) -> Int, value : Int) -> Int { f(f(value)) }
fn main { println(apply_twice(fn(x) { x + 1 }, 40)) }'42
42
Callback parameters use function types like `(Int) -> Int`; callback literals are `fn(x) { ... }`. Capturing mutable `Ref` state in a closure is a direct port of OCaml closures over refs.
```sh
moon run -c 'fn apply(f : (Int) -> Int raise, x : Int) -> Int raise { f(x) }
fn main raise { println(apply(fn(x) raise { if x == 0 { fail("zero") }; x + 1 }, 1)) }'
回调参数使用函数类型,如`(Int) -> Int`;回调字面量是`fn(x) { ... }`。在闭包中捕获可变`Ref`状态是OCaml引用上闭包的直接移植。
```sh
moon run -c 'fn apply(f : (Int) -> Int raise, x : Int) -> Int raise { f(x) }
fn main raise { println(apply(fn(x) raise { if x == 0 { fail("zero") }; x + 1 }, 1)) }'2
2
Raising callbacks must include `raise` in the parameter type and in the literal: `(Int) -> Int raise` and `fn(x) raise { ... }`. Inferred raising effects on `fn` literals are deprecated. For a narrower project error type, annotate: `fn(x) raise ProjectError { ... }`.
```sh
moon run -c $'fn apply(f : (Int) -> Int raise Error) -> Int raise Error { f(1) }\nfn main { let result : Result[Int, Error] = try? apply(fn(x) raise Error { if x > 0 { fail("x") } else { x } }); println(result is Err(_)) }'
抛出回调必须在参数类型和字面量中包含`raise`:`(Int) -> Int raise`和`fn(x) raise { ... }`。`fn`字面量上的推断抛出效果已被弃用。对于更窄的项目错误类型,请注解:`fn(x) raise ProjectError { ... }`。
```sh
moon run -c $'fn apply(f : (Int) -> Int raise Error) -> Int raise Error { f(1) }\nfn main { let result : Result[Int, Error] = try? apply(fn(x) raise Error { if x > 0 { fail("x") } else { x } }); println(result is Err(_)) }'true
true
Raising and non-raising callback types are **not** interchangeable. A raising closure cannot satisfy a non-raising slot, and vice versa. When porting OCaml callbacks whose implementation may later touch fallible APIs (filesystem, async, random), declare `raise` in the type from the start; do not hide failure inside the callback.
抛出和非抛出回调类型**不可**互换。抛出闭包无法满足非抛出槽的要求,反之亦然。移植OCaml回调时,如果其实现以后可能接触易出错API(文件系统、异步、随机),请从一开始就在类型中声明`raise`;不要在回调内部隐藏失败。Labelled and Default Arguments
标签与默认参数
sh
moon run -c $'fn greet(name? : String = "pdf") -> String { name }\nfn main { println(greet()); println(greet(name="moon")) }'sh
moon run -c $'fn greet(name? : String = "pdf") -> String { name }\nfn main { println(greet()); println(greet(name="moon")) }'moon
moon
Defaults attach to **labelled** parameters: `name? : T = default`. Call sites pass `name=value`. Do not place a default on an unlabelled positional parameter.
```sh
moon run -c 'fn inner(flag? : Bool = false) -> Bool { flag }
fn outer(flag? : Bool = false) -> Bool { inner(flag~) }
fn main { println(outer()); println(outer(flag=true)) }'
默认值附加到**标签化**参数:`name? : T = default`。调用站点传递`name=value`。不要为未标签化的位置参数设置默认值。
```sh
moon run -c 'fn inner(flag? : Bool = false) -> Bool { flag }
fn outer(flag? : Bool = false) -> Bool { inner(flag~) }
fn main { println(outer()); println(outer(flag=true)) }'false
false
true
true
`label~` forwards the current value of a same-named labelled argument through wrapper layers — the MoonBit counterpart to preserving OCaml optional-argument semantics across helpers.
```sh
moon run -c 'fn takes_view(xs : ArrayView[Int]) -> Int { xs.length() }
fn sample(xs? : ArrayView[Int] = []) -> Int { takes_view(xs) }
fn main { println(sample()); println(sample(xs=[1, 2, 3])) }'
`label~`将同名标签参数的当前值通过包装层转发——这是MoonBit中保留OCaml可选参数语义的对应方式。
```sh
moon run -c 'fn takes_view(xs : ArrayView[Int]) -> Int { xs.length() }
fn sample(xs? : ArrayView[Int] = []) -> Int { takes_view(xs) }
fn main { println(sample()); println(sample(xs=[1, 2, 3])) }'0
0
3
3
Optional view parameters can default to `[]`. Optional-only arguments must be passed by label (`sample(xs=[1, 2, 3])`, not `sample([1, 2, 3])`).
```sh
moon run -c 'fn f(a : Int, flag? : Bool = true, xs? : ArrayView[Int] = []) -> Int { if flag { a + xs.length() } else { a - xs.length() } }
fn main { println(f(3, xs=[1, 2], flag=false)); println(f(3, flag=true)); println(f(3)) }'
可选视图参数可以默认值为`[]`。仅可选参数必须通过标签传递(`sample(xs=[1, 2, 3])`,而非`sample([1, 2, 3])`)。
```sh
moon run -c 'fn f(a : Int, flag? : Bool = true, xs? : ArrayView[Int] = []) -> Int { if flag { a + xs.length() } else { a - xs.length() } }
fn main { println(f(3, xs=[1, 2], flag=false)); println(f(3, flag=true)); println(f(3)) }'1
1
3
3
3
3
After required positional arguments, multiple optionals can be supplied by label in any order.
Default values can call local functions, useful for structured-record or array defaults.
For an OCaml parameter that is genuinely "absent vs explicitly None vs explicitly Some(x)" — three distinct states — an `Option`-typed labelled argument (`x? : T? = None`) is acceptable, and `label~` forwarding still works through it. But this is rare. The common case is just `x? : T = default_value`; do not reach for `Option`-typed labels when a plain typed default would express the same thing.
在必需的位置参数之后,多个可选参数可以按任意顺序通过标签传递。
默认值可以调用局部函数,这对于结构化记录或数组默认值很有用。
对于OCaml中真正存在“缺失 vs 显式None vs 显式Some(x)”三种不同状态的参数,使用`Option`类型的标签化参数(`x? : T? = None`)是可接受的,且`label~`转发仍可在其中工作。但这种情况很少见。常见情况只需`x? : T = default_value`;当普通类型默认值可以表达相同含义时,不要使用`Option`类型的标签。Pattern Matching
模式匹配
sh
moon run -c 'enum E { A(Int); B }
fn f(e : E) -> Int { match e { A(n) if n > 0 => n; _ => 0 } }
fn main { println(f(A(3))); println(f(A(-1))); println(f(B)) }'sh
moon run -c 'enum E { A(Int); B }
fn f(e : E) -> Int { match e { A(n) if n > 0 => n; _ => 0 } }
fn main { println(f(A(3))); println(f(A(-1))); println(f(B)) }'3
3
0
0
0
0
Pattern guards belong on the same arm: `Pattern if condition => ...`. Do not split the `if` onto its own line.
Match arms run **top to bottom**. Put specific tuple or variant cases before broad wildcard or guarded catch-all arms — a guarded broad arm can make a later specific case behaviorally dead, and the compiler does not always catch that.
```sh
moon run -c 'fn main { let xs = [1, 2, 3]; match xs { [_, .. rest] => { let owned = [ for x in rest => x ]; println(rest.length()); println(owned.length()); println(owned[0]) }; _ => () } }'
模式守卫与分支在同一行:`Pattern if condition => ...`。不要将`if`拆到单独一行。
匹配分支按**从上到下**执行。将特定的元组或变体案例放在宽泛的通配符或守卫式捕获所有分支之前——守卫式宽泛分支可能会使后续的特定案例在行为上失效,且编译器并不总是能检测到这一点。
```sh
moon run -c 'fn main { let xs = [1, 2, 3]; match xs { [_, .. rest] => { let owned = [ for x in rest => x ]; println(rest.length()); println(owned.length()); println(owned[0]) }; _ => () } }'2
2
2
2
2
2
Array rest patterns (`.. rest`) bind read-only views. If an enum payload or API needs an owned `Array[T]`, copy with a comprehension: `[ for x in rest => x ]`.
数组剩余模式(`.. rest`)绑定只读视图。如果枚举负载或API需要自有`Array[T]`,使用推导式复制:`[ for x in rest => x ]`。Loops and Comprehensions
循环与推导式
sh
moon run -c 'fn main { let fixed : FixedArray[Int] = [1, 2, 3]; let doubled = [ for x in fixed => x * 2 ]; println(doubled.length()); println(doubled[2]) }'sh
moon run -c 'fn main { let fixed : FixedArray[Int] = [1, 2, 3]; let doubled = [ for x in fixed => x * 2 ]; println(doubled.length()); println(doubled[2]) }'3
3
6
6
```sh
moon run -c 'fn main { let pairs = [(1, 2), (3, 4)]; let firsts = [ for pair in pairs => pair.0 ]; println(firsts[0]); println(firsts[1]) }'
```sh
moon run -c 'fn main { let pairs = [(1, 2), (3, 4)]; let firsts = [ for pair in pairs => pair.0 ]; println(firsts[0]); println(firsts[1]) }'1
1
3
3
Comprehension syntax: `[ for x in xs => expr ]`. Use a simple identifier as the binder and destructure tuples or access fields inside the body.
```sh
moon run -c $'fn parse(s : String) -> Int raise { @string.parse_int(s) }\nfn main { let _ = [ for s in ["1"] => parse(s) ]; println("done") }'
推导式语法:`[ for x in xs => expr ]`。使用简单标识符作为绑定器,并在体内解构元组或访问字段。
```sh
moon run -c $'fn parse(s : String) -> Int raise { @string.parse_int(s) }\nfn main { let _ = [ for s in ["1"] => parse(s) ]; println("done") }'Error: calling function with error is not allowed inside list comprehension.
Error: calling function with error is not allowed inside list comprehension.
Comprehension bodies **cannot** call error-raising functions. Port OCaml `List.map`/`Array.map` with raising mappers as an explicit loop in a raising function: push each result into an output array and let the error propagate.
```sh
moon run -c 'fn main { let m : Map[Int, Int] = Map([]); m[1] = 10; m[2] = 20; let mut total = 0; for key in m.keys() { total += key }; println(total) }'
推导式体**不能**调用抛出错误的函数。将带有抛出映射器的OCaml `List.map`/`Array.map`移植为抛出函数中的显式循环:将每个结果推送到输出数组中,让错误传播。
```sh
moon run -c 'fn main { let m : Map[Int, Int] = Map([]); m[1] = 10; m[2] = 20; let mut total = 0; for key in m.keys() { total += key }; println(total) }'3
3
`for x in xs` iterates any iterable, not just arrays/views. `Map`, `@hashmap.HashMap`, ranges, and views all support it directly. Do not convert iterables to `Array` just to loop; reserve `.to_array()`/`.to_owned()` for cases that need an owned snapshot, sorting, indexing, or mutation.
```sh
moon run -c 'fn first_positive(xs : Array[Int]) -> Int? { let mut i = 0; while i < xs.length() { if xs[i] > 0 { break Some(xs[i]) }; i += 1 } nobreak { None } }
fn main { println(first_positive([-2, 0, 7]).unwrap()); println(first_positive([-2, 0]) is None) }'
`for x in xs`可迭代任何可迭代对象,不仅限于数组/视图。`Map`、`@hashmap.HashMap`、范围和视图都直接支持它。不要为了循环而将可迭代对象转换为`Array`;仅在需要自有快照、排序、索引或变异时保留`.to_array()`/`.to_owned()`。
```sh
moon run -c 'fn first_positive(xs : Array[Int]) -> Int? { let mut i = 0; while i < xs.length() { if xs[i] > 0 { break Some(xs[i]) }; i += 1 } nobreak { None } }
fn main { println(first_positive([-2, 0, 7]).unwrap()); println(first_positive([-2, 0]) is None) }'7
7
true
true
`while` loops may produce a value with `break value`; the branch when the condition becomes false is `nobreak { ... }`. The older `else` spelling is deprecated. Avoid the older functional `loop ... { ... }` form in new ports — MoonBit warns on it.
`while`循环可以通过`break value`产生值;当条件变为false时的分支是`nobreak { ... }`。旧的`else`写法已被弃用。在新移植代码中避免使用旧的函数式`loop ... { ... }`形式——MoonBit会对此发出警告。Surface Hazards
表面陷阱
- is a reserved keyword; avoid it as a local name, loop binder, or helper name to keep warning-enabled checks clean.
alias - On the JavaScript backend, also avoid sentinel names like for local test results — cross-target package tests can expose generated-JS name collisions.
undefined - Source files do not contain OCaml-style . Add package imports in
open, then call imported packages with theirmoon.pkg.@alias - For snippets that need extra packages, use an MBTX import block at the start:
moon run -c(comma-separated entries for multiple packages). The source-file formimport { "moonbitlang/x/crypto" }is rejected in command snippets.import "moonbitlang/core/encoding/ascii"
- 是保留关键字;避免将其用作局部名称、循环绑定器或辅助函数名称,以保持启用警告的检查干净。
alias - 在JavaScript后端,还要避免使用像这样的哨兵名称作为局部测试结果——跨目标包测试可能会暴露生成的JS名称冲突。
undefined - 源文件不包含OCaml风格的语句。在
open中添加包导入,然后使用其moon.pkg调用导入的包。@alias - 对于需要额外包的片段,在开头使用MBTX导入块:
moon run -c(多个包用逗号分隔)。源文件形式的import { "moonbitlang/x/crypto" }在命令片段中会被拒绝。import "moonbitlang/core/encoding/ascii"
Process and Random
进程与随机数
@envmoonbitlang/core/envargs() -> Array[String]current_dir() -> String?get_env_var(String) -> String?get_env_vars() -> Map[String, String]now() -> UInt64set_env_varunset_env_varcurrent_dir()x is Some(_).is_some()sh
moon run -c 'fn main { let r = @random.Rand::chacha8(seed=@ascii.encode("01234567890123456789012345678901")); println(r.uint(limit=256).to_string()); println(r.uint(limit=256).to_string()) }'@envmoonbitlang/core/envargs() -> Array[String]current_dir() -> String?get_env_var(String) -> String?get_env_vars() -> Map[String, String]now() -> UInt64set_env_varunset_env_varcurrent_dir()x is Some(_).is_some()sh
moon run -c 'fn main { let r = @random.Rand::chacha8(seed=@ascii.encode("01234567890123456789012345678901")); println(r.uint(limit=256).to_string()); println(r.uint(limit=256).to_string()) }'21
21
42
42
`@random` provides deterministic PRNGs. `Rand::new()` is **reproducible**, not OS entropy. Do not substitute `@random` or `@env.now()` for a cryptographic random source when porting security-sensitive OCaml code (AES IVs, salts, file keys); keep an explicit random provider or use a target-specific secure API.
`@random`提供确定性伪随机数生成器。`Rand::new()`是**可重现的**,而非操作系统熵源。移植安全敏感的OCaml代码(AES IV、盐、文件密钥)时,不要用`@random`或`@env.now()`替代加密随机源;保留显式的随机提供程序或使用特定于目标的安全API。Targets and Async I/O
目标与异步I/O
sh
moon run --target native -c $'#cfg(target="native")\nfn target_name() -> String { "native" }\n#cfg(not(target="native"))\nfn target_name() -> String { "other" }\nfn main { println(target_name()) }'sh
moon run --target native -c $'#cfg(target="native")\nfn target_name() -> String { "native" }\n#cfg(not(target="native"))\nfn target_name() -> String { "other" }\nfn main { println(target_name()) }'native
native
`#cfg(...)` on its own line immediately before a declaration provides target-specific implementations. Combining `#cfg` and the declaration on the same line leaves the attribute unused.
MoonBit async has no `await` keyword — async functions call other async functions directly.
- Keep CPU-bound pure transformations over `Bytes` synchronous.
- Make filesystem/network entry points async when they touch `moonbitlang/async`.
- Prefer async wrappers that load file contents into `Bytes`, then call the synchronous core. This avoids making every recursive helper async.
- Async entry points and async tests need `moonbitlang/async` in the relevant `moon.pkg`; syntax alone is not enough.
```sh
moon run --target native -c 'async fn main { println("async ok") }'
`#cfg(...)`单独一行,紧跟在声明之前,提供特定于目标的实现。将`#cfg`和声明放在同一行会导致属性未被使用。
MoonBit异步没有`await`关键字——异步函数直接调用其他异步函数。
- 保持`Bytes`上的CPU密集型纯转换为同步。
- 当涉及`moonbitlang/async`时,将文件系统/网络入口点设为异步。
- 优先使用异步包装器将文件内容加载到`Bytes`中,然后调用同步核心。这避免了让每个递归辅助函数都变为异步。
- 异步入口点和异步测试需要在相关的`moon.pkg`中添加`moonbitlang/async`;仅靠语法是不够的。
```sh
moon run --target native -c 'async fn main { println("async ok") }'Error: Cannot use async fn main
: package moonbitlang/async is not imported.
async fn mainError: Cannot use async fn main
: package moonbitlang/async is not imported.
async fn main
```sh
moon run --target native -c $'import {\n "moonbitlang/async",\n "moonbitlang/async/fs" @fs,\n}\nasync fn main {\n let result = try? @fs.read_file("/tmp/definitely-missing-fixture")\n println(result is Err(_))\n}'
```sh
moon run --target native -c $'import {\n "moonbitlang/async",\n "moonbitlang/async/fs" @fs,\n}\nasync fn main {\n let result = try? @fs.read_file("/tmp/definitely-missing-fixture")\n println(result is Err(_))\n}'true
true
If a port should keep its pure core available on non-native targets, put async/file-system wrappers in a native-only package rather than importing async into the core. Imports are package-level; `supported_targets = "+native"` controls backend inclusion. A single test file can be target-gated with `options(targets: { "file_test.mbt": [ "native" ] })`, but imports remain package-level and may report unused on non-native checks.
如果移植的代码需要在非原生目标上保留其纯核心,请将异步/文件系统包装器放在仅原生包中,而非将异步导入核心。导入是包级别的;`supported_targets = "+native"`控制后端包含。单个测试文件可以通过`options(targets: { "file_test.mbt": [ "native" ] })`进行目标门控,但导入仍然是包级别的,在非原生检查中可能会报告未使用。Testing
测试
Test file roles:
- : black-box tests. Call only public APIs through the package alias.
*_test.mbt - : white-box tests. Run inside the package; may test private helpers.
*_wbtest.mbt - : documentation with checked code blocks.
*.mbt.mdfor code that must compile and test;mbt checkfor illustrative-only snippets.mbt nocheck
Assertion style:
- for stable scalar or structural results (
@test.assert_eqbound; avoids the deprecatedDebug-based path).Show - Boolean assertions: — there is no
@test.assert_eq(condition, true)helper.@test.assert_true - accepts a named
@test.assert_eqargument for shared assertion helpers.msg= - Shared test helpers that call ,
@test.assert_eq, or fallible APIs should declarefailunless they use a narrower project error type.-> Unit raise Error - Pattern checks: or
assert_true(value is Pattern(...)).guard ... else { fail(...) } - Snapshots: for small values;
inspect(value, content="...")for complex values withdebug_inspect(value, content=...). If the expected snapshot is unknown, writeDebug, runinspect(value), then review the diff.moon test --update - Success paths through raising functions: call the fallible API directly and let the test fail on any error. Expected failures: and assert or inspect the result.
let result : Result[T, Error] = try? f() - Use for "this should raise"; match a specific error variant only when that variant is itself part of the compatibility contract.
Err(_)
sh
moon run -c 'fn helper() -> Unit raise Error { @test.assert_eq(true, true); @test.assert_eq([1, 2], [1, 2]) }
fn main raise { helper() }'sh
moon run -c 'import { "moonbitlang/core/test" }
fn helper() -> Unit raise { @test.assert_eq(true, true, msg="named assertion") }
fn main raise { helper(); println("ok") }'测试文件角色:
- :黑盒测试。仅通过包别名调用公共API。
*_test.mbt - :白盒测试。在包内运行;可以测试私有辅助函数。
*_wbtest.mbt - :带有已检查代码块的文档。
*.mbt.md用于必须编译和测试的代码;mbt check用于仅作说明的片段。mbt nocheck
断言风格:
- 用于稳定的标量或结构结果(需要
@test.assert_eq约束;避免已弃用的基于Debug的路径)。Show - 布尔断言:——没有
@test.assert_eq(condition, true)辅助函数。@test.assert_true - 接受命名的
@test.assert_eq参数,用于共享断言辅助函数。msg= - 调用、
@test.assert_eq或易出错API的共享测试辅助函数应声明fail,除非使用更窄的项目错误类型。-> Unit raise Error - 模式检查:或
assert_true(value is Pattern(...))。guard ... else { fail(...) } - 快照:用于小值;
inspect(value, content="...")用于带有debug_inspect(value, content=...)的复杂值。如果预期快照未知,编写Debug,运行inspect(value),然后审查差异。moon test --update - 抛出函数的成功路径:直接调用易出错API,让测试在任何错误时失败。预期失败:并断言或检查结果。
let result : Result[T, Error] = try? f() - 使用表示“此处应抛出错误”;仅当特定错误变体本身是兼容性契约的一部分时,才匹配该变体。
Err(_)
sh
moon run -c 'fn helper() -> Unit raise Error { @test.assert_eq(true, true); @test.assert_eq([1, 2], [1, 2]) }
fn main raise { helper() }'sh
moon run -c 'import { "moonbitlang/core/test" }
fn helper() -> Unit raise { @test.assert_eq(true, true, msg="named assertion") }
fn main raise { helper(); println("ok") }'ok
ok
For parser/serializer, encoder/decoder, or loader/writer pairs, pair focused edge tests with at least one public-API round-trip test. Round-trips catch ownership, byte/text, and object-boundary mistakes that isolated unit tests miss.
Cover the byte/text edges that OCaml callers depended on: non-ASCII, NUL, form-feed, high-bit bytes, integer overflow, empty input, and boundary offsets.
对于解析器/序列化器、编码器/解码器或加载器/写入器对,将聚焦的边缘测试与至少一个公共API往返测试配对。往返测试能捕获孤立单元测试遗漏的所有权、字节/文本和对象边界错误。
覆盖OCaml调用方依赖的字节/文本边缘情况:非ASCII、空字符、换页符、高位字节、整数溢出、空输入和边界偏移量。Command Cheatsheet
命令速查表
sh
moon run -c 'fn main { ... }' # quick language/API probe
moon check --warn-list +73 # fast type check with extra warnings
moon test --outline # list discovered tests
moon test # run all tests
moon test path/to/file_test.mbt # run one test file
moon test package/dir --filter 'glob' # targeted test glob
moon test --update # refresh snapshots, then review diff
moon test --target native # required for async I/O tests
moon coverage analyze > uncovered.log # coverage report
moon info && moon fmt # final interface update and formatValidation rule for each migration patch:
- Add or update tests before or with the ported code.
- Run targeted while developing.
moon test - Finish with ,
moon check --warn-list +73,moon test, andmoon info.moon fmt
sh
moon run -c 'fn main { ... }' # 快速语言/API探测
moon check --warn-list +73 # 带额外警告的快速类型检查
moon test --outline # 列出已发现的测试
moon test # 运行所有测试
moon test path/to/file_test.mbt # 运行单个测试文件
moon test package/dir --filter 'glob' # 按glob模式运行目标测试
moon test --update # 更新快照,然后审查差异
moon test --target native # 异步I/O测试必需
moon coverage analyze > uncovered.log # 覆盖率报告
moon info && moon fmt # 最终接口更新与格式化每个迁移补丁的验证规则:
- 在移植代码之前或同时添加或更新测试。
- 开发时运行目标。
moon test - 最后运行、
moon check --warn-list +73、moon test和moon info。moon fmt
Update Discipline
更新规范
When a migration teaches a reusable rule that contradicts or refines this guide, update this file with:
- The OCaml behavior being replaced.
- The MoonBit API or idiom chosen.
- A minimal probe and its observed output.
moon run -c - Any known incompatibility, target limitation, or deferred behavior.
当迁移发现与本指南矛盾或补充的可重用规则时,更新本文件,包含:
- 被替换的OCaml行为。
- 选择的MoonBit API或惯用写法。
- 最小化的探测及其观察到的输出。
moon run -c - 任何已知的不兼容性、目标限制或延迟实现的行为。