关键发现
1. Go 嵌入方法没有在 MoonBit 外层类型完整暴露
Go 的 Animal 嵌入 LoopTyme,外部调用者可直接使用被提升的循环类型方法。MoonBit 的 Animal 仅组合私有字段 loop_tyme,没有转发全部方法。
| 操作 | Go Animal | MoonBit Animal |
| 索引 | GetIndex() | get_index() |
| 循环大小 | GetSize() | 未在 Animal 暴露 |
| 顺向距离 | StepsTo(...) | 未在 Animal 暴露 |
| 逆向距离 | StepsBackTo(...) | 未在 Animal 暴露 |
| 最短距离 | StepsCloseTo(...) | 未在 Animal 暴露 |
| 比较 | Equals(...) | 未在 Animal 暴露 |
该模式影响 Animal、Beast、Constellation、Dipper、Dog、Duty、EarthBranch、HeavenStem、God、Nine、SixtyCycle、Week、Zodiac、Zone 等多种循环领域类型。
2. 构造入口经过语言化改造
Go 使用零值接收者工厂,MoonBit 使用类型静态方法:
// Go
SolarDay{}.FromYmd(year, month, day)
Animal{}.FromIndex(index)
// MoonBit
SolarDay::from_ymd(year, month, day)
Animal::from_index(index)
MoonBit 还额外公开了 new、validate、显式年月日 getter 等入口。功能可对应,但 API 形状不相同。
3. 返回值模型不同
| 场景 | Go | MoonBit |
| 可能失败的构造 | (*SolarDay, error) | Result[SolarDay, String] |
| 可能不存在的结果 | *DogDay / nil | DogDay? |
| 无返回值的失败操作 | error | Result[Unit, String] |
| 集合 | []Event | Array[Event] |
4. api.md 与实际导出接口不一致
api.md 使用 MoonBit 调用语法,但多处示例没有处理实际返回的 Result,因此不能按文档原样通过类型检查。
| 文档承诺或示例 | 实际公开接口 | 影响 |
LunarYear::from_year(2023) 后直接调用 getter | Result[LunarYear, String] | 必须先使用 ?、模式匹配或显式解包。 |
Week::from_name("日") 后直接使用对象 | Result[Week, String] | “抛出参数异常”的描述与 MoonBit 错误模型不符。 |
Gender::from_name("男") | 未导出 Gender::from_name | 文档承诺的按名称构造不可用。 |
builder.build() 生成事件对象 | Result[Event, String] | 不能直接作为 EventManager::update 的 Event 参数。 |
文档中共识别到 342 处 from_ymd、from_ym、from_year 或 from_name 调用,应按生成接口逐项确认错误处理。
5. 逐月胎神违反可空契约
api.md 明确说明闰月调用 LunarMonth.get_fetus() 返回 None。底层 FetusMonth::from_lunar_month 也确实返回 FetusMonth?,但外层方法通过 unwrap() 暴露为非可空 FetusMonth。
- 文档契约:闰月返回
None。
- 公开签名:
LunarMonth::get_fetus(Self) -> FetusMonth。
- 运行行为:闰月会在
unwrap() 处失败,而不是返回 None。
Go 参考实现的外层 GetFetus() 同样直接解引用可能为 nil 的结果,因此这里也是 Go 参考实现与 api.md 之间的契约问题。
6. 名称表和静态数据的暴露方式不同
Go 以公开可变切片暴露名称表,例如 AnimalNames;MoonBit 使用 animal_names() 返回数组。这影响调用形式、共享状态和可变性。
- Go:外部代码可直接读取或修改公开切片。
- MoonBit:外部代码通过函数获取数组,不是同一个公开变量。
- 同类差异覆盖节气、月份、神煞、宜忌、星期等多数静态名称表。
7. 枚举编码对应,但文档承诺的构造能力缺失
Go 使用 SOLAR_DAY、MAN 等公开整型常量;MoonBit 使用 EventType::SolarDay、Gender::Man 等 pub(all) enum 构造器。编码值和名称查询基本对应,但源码接口不兼容。
api.md 还承诺枚举支持 from_name(name),例如 Gender::from_name("男") 和 YinYang::from_name("阴");当前生成接口没有导出这些方法。from_code 对未知代码回退到默认枚举值,也没有返回 Option 或错误。
8. Provider 与 EventManager 不是相同公开模型
Go 直接暴露可赋值 Provider 全局变量;MoonBit 使用公开 Ref 和 setter。EventManager 的差异更明显:
- Go:
UpdateEvent、UpdateEventData、Remove,状态为公开字符串。
- MoonBit:
update、update_data、remove、额外的 set_data,状态为 Ref[Map[String, String]]。
- Go 更新事件时始终使用传入的旧名称定位记录;MoonBit 在事件自身名称非空时改用新名称作为 Map 键。
- 因此执行
update("情人节", event.name("西方传统情人节")) 时,Go 会替换旧记录,MoonBit 会新增新名称键且不会删除旧名称键。