使 OCaml 函数对于 int 列表和 float 列表具有多态性

问题描述 投票:0回答:4

有没有办法在 OCaml 中创建一个对整数和浮点数同样有效的多态 add 函数?例如,如果我有一个像这样的函数:

partialsums [1; 2; 3; 4; 5]
我应该得到
[1; 3; 6; 10; 15]
但是这个函数在
[1.; 2.; 3.; 4.; 5.]
上不起作用,因为在 OCaml 中整数和浮点数绝对不能混合。但是,如果我希望我的函数对于 int 列表和 float 列表同样有效,该怎么办?是否存在一个以 int 和 float 为子类型的通用类型?如果是这样,那是什么?我对此有点迷失。感谢您的帮助?

polymorphism ocaml
4个回答
20
投票

编辑:虽然这个答案具有理论价值,但你现在想阅读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 提供了一个很好的背景故事。


11
投票

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 -> ...

5
投票

您也可以像 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

0
投票

这是一个小型的、独立的示例,具有一流的模块。它实现了原题的多态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.
© www.soinside.com 2019 - 2024. All rights reserved.