OCaml to MoonBit Migration 是一份面向 OCaml 到 MoonBit 迁移的指南技能。其核心主张是:先移植行为、数据不变量与公开契约,只有在源语义被分类之后才翻译语法。指南指出最常见的移植缺陷是把 OCaml 的 string(字节序列)静默强转为 MoonBit 的 String(UTF-16 文本),因此必须在选择类型之前按含义对每个字段进行分类。
迁移工作流
指南建议按以下步骤推进:清点 OCaml 模块边界,包括公开类型、函数、异常、可选参数、可变状态、惰性/延迟状态、C/Unix/文件系统依赖以及现有测试或黄金基准;在选类型前逐字段按含义分类每个 OCaml string;在编码前先确定 MoonBit 包边界与导入,导入需写入 moon.pkg,MoonBit 源文件不使用 OCaml 风格的 open;一次移植一个行为切片并配套测试,先搭薄公开 API 骨架,再填充解析器/序列化器/算法内部;用 moon run -c 探测不确定的语言或库行为,必要时辅以小型 OCaml toplevel 探针,保持探针最小化;每个切片以 moon check、moon test、moon info、moon fmt 收尾,可用 moon check --warn-list +... 提高严格度。
类型映射速查表
指南给出 OCaml 用法到 MoonBit 默认类型的映射:二进制载荷、文件内容、压缩/加密/校验数据、解析器输入用 Bytes 或 BytesView;人类可读文本、诊断信息、真正 Unicode 的标签用 String;已知 0..255 范围的单字节用 Byte;索引、计数、小标识符、刻意的有符号 32 位回绕用 Int;文件偏移、序列化位置、大对象编号用 Int64 或 UInt64;定长 OCaml 数组用 FixedArray[T];可变可增长构建器用 Array[T];只读序列参数用 ArrayView[T] 或 BytesView;编译期查找表用 ReadOnlyArray[T];确定性迭代的键值查找用 Map[K, V];OCaml Buffer.t 字节构建器用 @buffer.Buffer;OCaml ref 用 Ref[T];OCaml variant 用 enum,内部状态常用 priv enum;OCaml record 用 struct,不可变更新用 { ..old, field: value };OCaml 异常流用 suberror 加受检 raise;惰性/延迟状态用 Lazy[T];需要回绕的 OCaml int32 用 Int,需要宽算术的用 Int64 或 UInt64;OCaml float 用 Double。当前工具链没有 Int32 类型。
Bytes 与 String
OCaml String.length 计字节,MoonBit String::length() 计 UTF-16 码元,同一源字符在两语言中位置不同且差异是静默的。String::char_length() 计 Unicode 标量,两者都不是字节。面向字节的格式应使用 Bytes/BytesView,Bytes 与 String 之间只能通过具名编码辅助函数(@ascii.encode、@utf8.encode 等)转换,并记录编码假设。Bytes::from_array 产生不可变的自有 Bytes,之后修改源数组不影响冻结值;移植 OCaml Bytes 修改器时应改为 BytesView -> Bytes 变换,或把状态保留在 Array[Byte]/FixedArray[Byte] 直到最终冻结。Bytes::new(length) 总是零填充且需显式长度。Bytes::copy 已弃用,因为 Bytes 不可变;需要物理副本时用 Bytes::makei(len, i => src[i])。b"..." 是编译期字节串字面量,类型为 Bytes,用于 ASCII 格式语法,无需 @ascii 导入或运行时调用;不要用 String 拼接构建二进制文件格式。规范字节构建器是 @buffer.Buffer,用 @buffer.new() 构造,write_bytes 追加静态 ASCII,write_byte 追加单字节,write_bytes(@ascii.encode(text)) 追加动态文本,contents() 冻结。
BytesView
BytesView 是 ArrayView 的字节序列对应物,是廉价切片,暴露只读字节操作,支持带剩余模式的模式匹配。只读字节 API 优先用 BytesView,仅在明确所有权边界调用 .to_owned()。Bytes 可赋值给 BytesView 参数,因此单一 API 可接受自有或借用输入;返回的 BytesView 会保持其底层 Bytes 存活。反向不自动:BytesView 不能通过需要自有 Bytes 的类型检查,包括原生 extern 声明,应视为所有权边界并显式调用 .to_owned()。相等比较在视图为左操作数时可用;若 Bytes 在左,先切片(bytes[:])再比较。NUL(0) 与换页符(12) 在 Bytes/BytesView 中是普通字节,应逐字移植 OCaml 字节谓词。
整数
标量类型有 Byte、Int16、UInt16、Int、UInt、Int64、UInt64、Float、Double,没有 Int32。对整数字面量调用方法需加括号:(65).to_byte(),而非 65.to_byte()。Int 算术按 2^32 取模并以有符号解释,符号运算符直接对应 OCaml:& (land)、| (lor)、^ (lxor)、<< (lsl)、>>(有符号算术右移)。Int 右移是算术的,移位计数被掩码到 5 位,所以 1 << 32 == 1;OCaml Int32.shift_right_logical 需先重解释为无符号、移位、再重解释回有符号。数值转换(.to_uint())与位重解释(.reinterpret_as_uint())不同。@string.parse_int 拒绝越界十进制,但手写数字累加在 Int 中会静默回绕,偏移、长度、对象编号、序列化计数器应在 Int64/UInt64 中累加并在收窄前做边界检查。超过 0x7FFFFFFF 的 Int 字面量为负;按无符号字节序排序的表对有符号 Int 比较并非有序,应通过 UInt 比较或存为 Int64/UInt64。% 保留左操作数符号,应把 (a - b) mod 256 归一化到 0..255 再转 Byte。UInt64 算术按 2^64 取模,无符号右移是逻辑的。位取反用 Int::lnot(value) 或 value.lnot(),布尔取反用 !expr,不要逐字移植 OCaml 的 not x。
浮点
Double::to_string() 不能直接替代 OCaml string_of_float 或 Printf "%.12g":整数值渲染为 2(而非 2.),无穷渲染为 Infinity/-Infinity(而非 inf/-inf),小值用科学计数法。当摘要、文件格式或快照依赖精确拼写时,应把 OCaml 格式化器移植为显式边界函数。溢出产生 Infinity,除以无穷产生 0。没有 ** 运算符,用 @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 无效,这些仅作为方法存在。
数据结构与查找表
OCaml variant 映射为 MoonBit enum,对测试中会检查的类型派生 Debug、Eq。跨包边界或构建异构外观的 enum 数组时,优先显式 Type::Constructor 形式或具体元素类型标注,以保持 moon check --warn-list +73 有用。内部辅助用 priv enum/priv struct,避免在私有辅助上派生 trait,除非调试路径确实使用。记录更新是 { ..old, field: value },直接替代 OCaml 的 { old with field = value }。单字段 struct 字面量在另一表达式中使用字段简写时,写尾随逗号({ values, })以消除歧义块警告。当 OCaml 函数返回宽 variant 而后续代码依赖非正式不变量时,引入窄的私有 MoonBit enum 表示检查后状态,使不可能的兜底分支在类型边界显式化。对于大型 OCaml 映射表,优先使用紧凑的接受 ArrayView 的范围表((first, last, base) 加小型例外/序列表),而非机械展开每个条目,生成的 MoonBit 源码更小、原生目标验证更快,且查找行为确定。
限制与说明
指南中的探针在 moon 0.1.20260512-class 工具链上验证;若本地工具链更新且行为关键,应重跑相关探针。资料未提供该技能的收费、价格、平台支持范围或具体安装步骤等信息。