回

13 / 17 · Files, Modules, and Programs

模块是一层墙

struct 是实现。签名决定外面能看见什么。

文件即模块:counter.ml 就是模块 Counter。里面的 let 默认都看得见。签名把不想给人用的函数藏起来,也可以把 type t = int 写成抽象的 type t,外面就只能当黑盒。

设计顺序是书里反复说的:先写调用的地方想要的接口,再写实现。不要先把具体类型漏出去,回头再补墙。

例子

utop
module Counter = struct
  let zero = 0
  let bump n = n + 1
  let render n = Int.to_string n
end
module Counter : sig
  val zero : int
  val bump : _ -> _
  val render : _ -> _
end
let c = Counter.bump (Counter.bump Counter.zero)
val c : int = 2
Counter.render c
- : string = "2"

函子是「模块到模块的函数」

Inc 吃一个提供 x : int 的模块,吐出一个更大的模块。参数是签名,不是值。下面这段是书里的形状,书桌不跑函子语法;你要感到的是:实现可以换,签名不变。

module type X = sig
  val x : int
end
module Inc (M : X) = struct
  let y = M.x + 1
end
module A = Inc (struct let x = 3 end)

改一改

改完再跑

解释器覆盖书前半的核心:函数、标签参数、模式、记录、变体、ref、模块,以及简化的 Map 和 JSON。没有 GADT 细化、Async 和独立编译。

先预测,再对照

1 / 2

签名里只写 type t,不写 type t = int。外面能做什么?

2 / 2

函子的参数是?