有没有办法在 OCaml 中创建一个对整数和浮点数同样有效的多态 add 函数?例如,如果我有一个像这样的函数:
partialsums [1; 2; 3; 4; 5]
我应该得到 [1; 3; 6; 10; 15]
但是这个函数在 [1.; 2.; 3.; 4.; 5.]
上不起作用,因为在 OCaml 中整数和浮点数绝对不能混合。但是,如果我希望我的函数对于 int 列表和 float 列表同样有效,该怎么办?是否存在一个以 int 和 float 为子类型的通用类型?如果是这样,那是什么?我对此有点迷失。感谢您的帮助?
编辑:虽然这个答案具有理论价值,但你现在想阅读neo的答案。
对于某些类型t,定义一个模块,
module type Semigroup = sig
type t
val add : t -> t -> t
end
以及一些实用函数,例如
partialsums
,它们在函子内部依赖于此,
module Utils (S : Semigroup) = struct
let partialsums xs =
match xs with
| [] -> []
| (x::xs) ->
List.rev (snd (List.fold_left
(fun (acc, ys) x -> let y = S.add acc x in (y, y::ys)) (x, [x]) xs))
end
您可以获得专门针对特定类型的
partialsums
t,
module IntUtils = Utils(struct type t = int
let add = (+) end)
module FloatUtils = Utils(struct type t = float
let add = (+.) end)
let int_test = IntUtils.partialsums [1; 2; 3; 4] ;;
let float_test = FloatUtils.partialsums [1.0; 2.0; 3.0; 4.0]
这很酷,但也有点乏味;您仍然需要为函数添加特定于类型的前缀,但至少您只需编写函数一次。这只是模块系统很棒。
使用 White、Bour 和 Yallop 的 Modular Implicits (2014),你可以写,
implicit module Semigroup_int =
type t = int
let add = (+)
end
implicit module Semigroup_float =
type t = float
let add = (+.)
end
implicit module Semigroup_string =
type t = string
let add = (^)
end
let add {S : Semigroup} x y = S.add x y
这将允许定义泛型 and 重载
partialsums
,
let partialsums xs =
match xs with
| [] -> []
| (x::xs) ->
List.rev (snd (List.fold_left
(fun (acc, ys) x -> let y = add acc x in (y, y::ys)) (x, [x]) xs))
所以现在它对于整数和浮点数同样有效!
let int_test = partialsums [1; 2; 3; 4] ;;
let float_test = partialsums [1.0; 2.0; 3.0; 4.0]
let string_test = partialsums ["a"; "b"; "c"; "d"]
显然已经进行了多次尝试来统一 ML 模块系统和 Haskell 的类型类概念。参见例如Modular Type Classes (2007) Dreyer、Harper 和 Chakravarty 提供了一个很好的背景故事。
int list
和float list
唯一常见的类型是'a list
,即任何类型的列表。由于元素类型可以是任何类型,因此没有可以应用于元素的特定操作。所以没有直接的方法来编写你想要的函数。
如果您愿意将列表与对其元素进行操作的
+
函数捆绑在一起,您就可以通过这种方式解决问题。
let partialsums plus list =
List.rev
(List.fold_left
(fun l n ->
if l = [] then [n] else (plus (List.hd l) n) :: l)
[] list)
# partialsums (+) [1;3;5;7];;
- : int list = [1; 4; 9; 16]
# partialsums (+.) [1.;3.;5.;7.];;
- : float list = [1.; 4.; 9.; 16.]
在这种情况下,列表元素不必是数字:
# partialsums (^) ["a"; "b"; "c"; "d"];;
- : string list = ["a"; "ab"; "abc"; "abcd"]
另一个常见的解决方案是使用变体类型:
let numlist = Flist of float list | Ilist of int list
liet partialsums (list: numlist) =
match list with
| Flist l -> ...
| Ilist l -> ...
您也可以像 Base 那样尝试一流的模块(https://github.com/janestreet/base/blob/57240d0d8403031f37e105351d7d928a6aea1524/src/container.ml#L17),例如:
let sum (type a) ~fold (module M : Commutative_group.S with type t = a) t ~f =
fold t ~init:M.zero ~f:(fun n a -> M.(+) n (f a))
;;
这为您提供了相当轻量级的语法:
List.sum (module Int) [1; 2; 3; 4; 5] ~f:Fn.id
这是一个小型的、独立的示例,具有一流的模块。它实现了原题的多态add函数。
module type Add = sig
type t
val add : t -> t -> t
end
module I = struct
type t = int
let add = ( + )
end
module F = struct
type t = float
let add = ( +. )
end
let add (type a) (module M : Add with type t = a) x y =
M.add x y
然后你可以像这样使用
add
:
# add (module I) 1 2;;
- : I.t = 3
# add (module F) 1. 2.;;
- : F.t = 3.