Tyme4MB 对外接口一致性检查报告

比较范围:MoonBit 包 tyme、仓库内 Go 参考实现 tyme4go/tyme,以及公开调用契约 api.md

检查日期:2026-08-05 检查维度:公开类型、方法、构造、返回值、数据、枚举、全局状态、文档契约与测试基线
最终判断:未做到公开 API 1:1 复刻,也未完整符合 api.md Go 与 MoonBit 的公开领域类型和实现文件数量对应,主要领域能力已有移植;但方法集合、构造与错误处理、可空契约、枚举构造、静态数据、Provider、EventManager 状态语义及文档示例均存在已确认差异。

检查概览

111 / 111
公开领域类型覆盖
117 / 117
非测试实现文件覆盖
通过
MoonBit:moon test tyme
失败
Go:go test ./...
不符合
api.md 调用契约
非严格一致
公开 API 结论

一致性矩阵

维度判断说明
领域类型一致Go 与 MoonBit 均识别到 111 个公开领域类型,没有类型级缺失;该统计不等同于方法和行为完整。
实现模块一致排除测试文件并规范化文件名后,两边均为 117 个实现文件。
主要领域能力已有移植历法、干支、节气、八字、童限、神煞等主体类型均有对应实现,但仅凭文件与类型覆盖不能证明全部行为等价。
方法集合不一致Go 嵌入类型提升的方法未全部转发到 MoonBit 外层类型。
构造方式不一致Go 使用零值接收者或顶层 New 函数;MoonBit 使用类型静态方法并额外暴露 new/validate。
错误与可空值部分适配多数 Go 指针、nil、error 被改为 Option 与 Result;但 LunarMonth.get_fetus() 没有保持文档承诺的可空语义。
api.md 调用契约不符合大量示例未处理 Result;枚举 from_name 缺失;EventBuilder 和逐月胎神示例与实际签名不符。
公开静态数据不一致Go 公开可变切片;MoonBit 多数改为返回数组的公开函数。
枚举不完整对应编码和值名称基本对应,但引用方式不同,且 api.md 承诺的按名称构造没有实现。
Provider 与全局状态不一致直接可赋值全局变量被改为 Ref、setter 或 Map 状态。
EventManager不一致公开方法名、状态类型和数据入口不同;事件重命名时已确认存在键选择行为差异。
测试基线部分通过moon test tyme 通过;go test ./...RabByungDay.go 的格式化参数类型错误而失败。

关键发现

1. Go 嵌入方法没有在 MoonBit 外层类型完整暴露

Go 的 Animal 嵌入 LoopTyme,外部调用者可直接使用被提升的循环类型方法。MoonBit 的 Animal 仅组合私有字段 loop_tyme,没有转发全部方法。

操作Go AnimalMoonBit 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 还额外公开了 newvalidate、显式年月日 getter 等入口。功能可对应,但 API 形状不相同。

3. 返回值模型不同

场景GoMoonBit
可能失败的构造(*SolarDay, error)Result[SolarDay, String]
可能不存在的结果*DogDay / nilDogDay?
无返回值的失败操作errorResult[Unit, String]
集合[]EventArray[Event]

4. api.md 与实际导出接口不一致

api.md 使用 MoonBit 调用语法,但多处示例没有处理实际返回的 Result,因此不能按文档原样通过类型检查。

文档承诺或示例实际公开接口影响
LunarYear::from_year(2023) 后直接调用 getterResult[LunarYear, String]必须先使用 ?、模式匹配或显式解包。
Week::from_name("日") 后直接使用对象Result[Week, String]“抛出参数异常”的描述与 MoonBit 错误模型不符。
Gender::from_name("男")未导出 Gender::from_name文档承诺的按名称构造不可用。
builder.build() 生成事件对象Result[Event, String]不能直接作为 EventManager::updateEvent 参数。

文档中共识别到 342 处 from_ymdfrom_ymfrom_yearfrom_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_DAYMAN 等公开整型常量;MoonBit 使用 EventType::SolarDayGender::Manpub(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:UpdateEventUpdateEventDataRemove,状态为公开字符串。
  • MoonBit:updateupdate_dataremove、额外的 set_data,状态为 Ref[Map[String, String]]
  • Go 更新事件时始终使用传入的旧名称定位记录;MoonBit 在事件自身名称非空时改用新名称作为 Map 键。
  • 因此执行 update("情人节", event.name("西方传统情人节")) 时,Go 会替换旧记录,MoonBit 会新增新名称键且不会删除旧名称键。

MoonBit 改造方案(待执行)

目标:在遵循 MoonBit 的 ResultOption、trait、静态方法和小写命名风格的前提下,使公开能力尽可能接近 Go 参考实现,并让 api.md 中的可执行示例与真实导出接口一致。

范围边界:本方案只处理 MoonBit 相对既定目标的差异。Go 参考实现与 api.md 互相矛盾的内容单独列为“待讨论项”,在结论确定前不直接改写 MoonBit。

改造原则与决策顺序

  1. 优先保持 MoonBit 类型安全:可失败构造继续使用 Result,可能不存在的值继续使用 Option,不通过隐式 unwrap() 掩盖错误。
  2. 公开能力优先对齐:Go 通过嵌入类型获得的公开方法,在 MoonBit 组合模型下通过明确的外层转发方法提供。
  3. 名称和调用风格遵循现有 MoonBit 约定,例如 from_ymdget_lunar_day;不机械复制 Go 的大小写命名。
  4. 数据与状态语义优先保持行为一致;如果 Go 的可变全局变量无法安全照搬,则保留 MoonBit 的 Ref,但保证更新、读取和重命名结果一致。
  5. 决策顺序固定为:用户确认的契约 → Go 与 api.md 一致的部分 → MoonBit 类型安全和风格 → 尚未确认的历史行为。

分阶段实施

阶段工作内容完成标准
0. 冻结基线pkg.generated.mbti 作为 MoonBit 真实公开接口;整理 Go、MoonBit、api.md 三方签名矩阵;把 Go/API 冲突移入待讨论清单。每个差异都有唯一目标、来源和处理决定,不再以文件数量推断 API 完整。
1. 公开方法面补齐 LoopTyme 组合类型的公开转发:get_sizesteps_tosteps_back_tosteps_close_toequals;同步检查所有受影响的循环类型。MoonBit 外层类型可执行 Go 外部调用者可执行的对应循环操作,且不暴露内部字段。
2. 构造与错误契约逐项决定构造器继续返回 Result 还是提供安全的便利入口;修正 api.md 示例中的解包方式;补齐或明确枚举名称构造。文档示例可通过 MoonBit 类型检查,错误和空值语义在签名中可见。
3. 可空值与领域边界LunarMonth::get_fetus 恢复为 FetusMonth?,移除外层 unwrap();扫描其他由 Go nil 转译而来的强制解引用。闰月和其他不存在结果均返回 None,不因正常边界输入触发运行时失败。
4. EventManager 与 Provider统一事件键、重命名、删除、全量替换和 Provider 注入语义;保留 MoonBit 的显式状态容器,但补齐与 Go/API 对应的操作入口。针对新增、更新、改名、删除、批量替换和 Provider 切换都有跨实现测试。
5. API 文档与回归从生成接口抽取可编译示例;补充公开 API 的契约测试、Go/MoonBit 交叉结果测试和边界测试;重新生成报告数据。moon test tyme 通过,新增 API 契约测试通过,报告中的每项结论都有源码或测试证据。

优先级与验收门槛

  • P0:先处理会导致文档示例无法类型检查或正常边界输入失败的问题:Result 使用方式、get_fetus 可空契约、EventBuilder 参数类型。
  • P1:补齐循环类型的公开方法转发,统一 EventManager 的键和状态行为。
  • P2:处理静态数据表、枚举辅助入口、废弃接口和文档命名细节。
  • 验收:不以“文件数相同”作为完成条件;必须同时满足公开签名可发现、示例可编译、边界行为可复现、Go/MoonBit 交叉测试结果一致。

暂不纳入 MoonBit 改造的 Go/API 冲突

以下问题需要单独讨论并确定目标,不在本方案中擅自选择:

  • api.md 说明闰月 get_fetus() 返回 None,但 Go 外层实现直接解引用可能为 nil 的结果。
  • api.md 承诺枚举 from_name,Go 和当前 MoonBit 都没有统一实现该入口。
  • api.md 的构造示例省略错误处理,而 Go 实际使用指针加 error,MoonBit 使用 Result
  • api.md 包含 EventManager::set_data,Go 参考实现当前没有对应公开方法。

综合判断

当前项目是“类型和实现文件覆盖完整、主要领域能力已移植”的 MoonBit 适配,不是“对外 API 完全一致”的 1:1 复刻,也未完整符合 api.md

若目标是严格 API 兼容,优先修正 api.md 中未处理的 Result、补齐枚举 from_name、恢复逐月胎神的 Option 契约、补齐 LoopTyme 方法转发,并统一 EventManager 的重命名和全局状态语义。若目标只是领域计算能力对应,则现有语言化构造、Option/Result 和 enum 适配可以保留,但文档必须明确这些差异。

主要证据来源