OCaml实现类似Haskell类型类的可相加类型机制问询
OCaml中类型类的替代方案:实现可相加类型的通用操作
问题背景
我正在学习OCaml,之前写论文接触过一点Haskell,想知道OCaml里有没有类似Haskell类型类的机制,用来表示int、float、string这类可相加的类型。我知道OCaml没有原生类型类,但想找能实现相同功能的替代方法。
最初尝试的代码及错误
一开始我尝试定义带通用add方法的模块类型,但写的sum函数报错了:
module type Addable = sig type t val add : t -> t -> t val zero : t val one : t end module Addable_int : Addable with type t = int = struct type t = int let add a b = a + b let zero = 0 let one = 1 end module Addable_float : Addable with type t = float = struct type t = float let add a b = a +. b let zero = 0. let one = 1. end let rec sum (type a) (module A : Addable with type t = a) = match l with | [] -> A.zero | x::l -> A.add x (sum (module A) l)
报错信息:"This expression has type 'a but an expression was expected of type a The type constructor a would escape its scope"
修正后的代码及后续疑问
后来我修正了代码,实现了能正常运行的通用sum函数,但想知道能不能省去每次调用sum时都传入(module IntNumber)或(module FloatNumber)的步骤,进一步抽象这个行为:
module type NumberType = sig type t val add : t -> t -> t val zero : t val one : t end module IntNumber : NumberType with type t = int = struct type t = int let add x y = x + y let zero = 0 let one = 1 end module FloatNumber : NumberType with type t = float = struct type t = float let add x y = x +. y let zero = 0.0 let one = 1.0 end module Addbl (M: NumberType) = struct type t = M.t let add x y = M.add x y let zero = M.zero let one = M.one end let rec sum : type a. (module NumberType with type t = a) -> a list -> a = fun (module M) l -> match l with | [] -> M.zero | x::t -> M.add x (sum (module M) t) let () = let xs = [1.;2.;3.] in let _ = print_float @@ sum (module FloatNumber) xs in let _ = print_string "\n" in let xsi = [1;2;3;4] in print_int @@ sum (module IntNumber) xsi
两种简化调用的方案
OCaml没有Haskell类型类的自动推导,但可以通过以下两种方式省去手动传模块的步骤:
1. 类型包装器方案(纯核心语法)
通过定义一个带phantom type的包装类型,把模块和具体类型绑定,让OCaml通过列表类型自动推导所需模块:
module type NumberType = sig type t val add : t -> t -> t val zero : t end (* 包装类型,关联具体的NumberType模块 *) type 'a num = Num : (module NumberType with type t = 'a) -> 'a num (* 为int注册对应的实例 *) let int_num : int num = Num (module struct type t = int let add = (+) let zero = 0 end) (* 为float注册对应的实例 *) let float_num : float num = Num (module struct type t = float let add = (+.) let zero = 0.0 end) (* 改写sum,通过包装实例自动获取模块 *) let rec sum : type a. a num -> a list -> a = fun (Num (module M)) l -> match l with | [] -> M.zero | x::rest -> M.add x (sum (Num (module M)) rest) (* 调用时,OCaml会根据列表类型自动匹配对应的实例 *) let () = print_float (sum float_num [1.; 2.; 3.]); print_newline (); print_int (sum int_num [1; 2; 3; 4])
2. 模块化隐式方案(OCaml 4.08+扩展)
如果用的是OCaml 4.08及以上版本,可以启用模块化隐式扩展,这是最接近Haskell类型类的实现:
编译时需要加参数-extension modular_implicits,toplevel中则执行#require "modular-implicits"。
(* 定义隐式模块类型 *) module type implicit NumberType = sig type t val add : t -> t -> t val zero : t end (* 为int实现隐式模块 *) implicit module IntNumber : NumberType with type t = int = struct type t = int let add = (+) let zero = 0 end (* 为float实现隐式模块 *) implicit module FloatNumber : NumberType with type t = float = struct type t = float let add = (+.) let zero = 0.0 end (* 定义sum函数,隐式获取NumberType模块 *) let rec sum : type a. {implicit NumberType with type t = a} -> a list -> a = fun module M l -> match l with | [] -> M.zero | x::rest -> M.add x (sum rest) (* 调用时无需手动传模块,OCaml自动推导 *) let () = print_float (sum [1.; 2.; 3.]); print_newline (); print_int (sum [1; 2; 3; 4])
方案对比
- 类型包装器是纯核心OCaml语法,兼容性拉满,但需要手动给每个类型注册实例;
- 模块化隐式更贴近类型类的使用体验,但属于扩展特性,部分工具链支持可能有限;
- 可根据项目的兼容性要求和代码风格选择合适的方案。
内容的提问来源于stack exchange,提问作者Rhohen
相关产品推荐
相关产品推荐

