某些功能语言(例如OCaml)具有内置的机制来实现抽象数据类型,因此强制执行某些不变式。没有这种机制的语言依赖于用户“不看地毯”来强制不变式。
OCaml中的抽象数据类型
在OCaml中,模块用于构造程序。模块具有实现和签名,后者是模块中定义的值和类型的一种摘要,而前者提供了实际的定义。与.c/.h
C程序员熟悉的双连画相比,这可以粗略地比较。
例如,我们可以Fraction
像这样实现模块:
# module Fraction = struct
type t = Fraction of int * int
let rec gcd a b =
match a mod b with
| 0 -> b
| r -> gcd b r
let make a b =
if b = 0 then
invalid_arg "Fraction.make"
else let d = gcd (abs a) (abs b) in
Fraction(a/d, b/d)
let to_string (Fraction(a,b)) =
Printf.sprintf "Fraction(%d,%d)" a b
let add (Fraction(a1,b1)) (Fraction(a2,b2)) =
make (a1*b2 + a2*b1) (b1*b2)
let mult (Fraction(a1,b1)) (Fraction(a2,b2)) =
make (a1*a2) (b1*b2)
end;;
module Fraction :
sig
type t = Fraction of int * int
val gcd : int -> int -> int
val make : int -> int -> t
val to_string : t -> string
val add : t -> t -> t
val mult : t -> t -> t
end
现在可以像下面这样使用该定义:
# Fraction.add (Fraction.make 8 6) (Fraction.make 14 21);;
- : Fraction.t = Fraction.Fraction (2, 1)
任何人都可以绕过内置的安全网直接产生类型分数的值Fraction.make
:
# Fraction.Fraction(0,0);;
- : Fraction.t = Fraction.Fraction (0, 0)
为了防止这种情况,可以隐藏如下类型的具体定义Fraction.t
:
# module AbstractFraction : sig
type t
val make : int -> int -> t
val to_string : t -> string
val add : t -> t -> t
val mult : t -> t -> t
end = Fraction;;
module AbstractFraction :
sig
type t
val make : int -> int -> t
val to_string : t -> string
val add : t -> t -> t
val mult : t -> t -> t
end
创建an的唯一方法AbstractFraction.t
是使用该AbstractFraction.make
功能。
Scheme中的抽象数据类型
Scheme语言没有与OCaml相同的抽象数据类型机制。它依靠用户“不看地毯下面”来实现封装。
在Scheme中,习惯上定义谓词,例如fraction?
识别值,从而有机会验证输入。以我的经验,主要用法是让用户验证其输入(如果他伪造了一个值),而不是在每个库调用中验证输入。
但是,有几种策略可以强制对返回值进行抽象化,例如返回一个闭包,该闭包在应用时会产生该值,或者返回对该库管理的池中某个值的引用-但我在实践中从未见过。