我在这个问题上卡住了,我必须:将日期类型定义为一个三段式,其中第一段是一个用整数表示的年份,第二段是一个来自[1...12]区间的整数,表示月份,最后一段是用一个整数表示一天。
type date = { year: int; month: int; day:int};;
{year = 2012; month = 12; day = 21};;
这很好用,但是对于参数月份,我需要有1到12的整数作为输入。
如果你想对月份使用一个变体并列举所有的月份。你可以对日数做同样的处理,但我会避免这样做。
如果你想要一个更普遍的概念,如范围类型:你不希望,因为月有30或31天,二月有28-29取决于闰年,所以它是依赖于范围...... 如果这个检查是可用的,那么你想要哪个范围?你所需要的检查是非常具体的:作为一个例子,我只在cpp中看到过这个扩展,你被迫从范围中使用int(所以非常有用)。因为这个扩展需要静态的int,所以你不能真正的使用日期。OCaml不能这么做,因为所有的int都是 int
而不是30或31。通常的变通方法是创建一个函数,取三个int验证它们是否在有效范围内,然后返回 date option
并且也让它从外部私有化,这样你就不能破坏这个不变式。我们称之为智能构造函数。
module SafeDate : sig
type date = private { year: int; month: int; day:int}
val create: int -> int -> int -> date option
end = struct
type date = { year: int; month: int; day:int}
let create year month day = if (* put formula/code to say if correct *) then Some{year; month; day} else None
end
不,但你可以防止意外。
OCaml(和其他地方)的时间和日期库通常会确保日期对象的组件落在正确的范围内,而不需要特殊的类型系统技巧。
在OCaml中,一个好的解决方案可能是将日期的字段暴露为只读。这意味着模块的用户将无法使用符号来创建这种类型的记录。{year=...; month=...; day=...}
. 这是通过提供一个显式模块接口(.mli文件)来实现的,该接口将日期类型声明为 private
:
(* Date.mli *)
type t = private {
year: int;
month: int;
day: int;
}
(** Create a date object if and only if given acceptable values *)
val create : year:int -> month:int -> day:int -> t option
执行情况(Date.ml
文件)照旧。特别是,没有 private
类型定义中的关键字。
(* Date.ml *)
type t = {
year: int;
month: int;
day: int;
}
let create ...
你也可以在这一点上有所创新。例如,你可以让库安全地公开由年月日派生的字段,如自年初以来的天数等。