【问题标题】:Make OCaml function polymorphic for int lists and float lists使 OCaml 函数对 int 列表和 float 列表具有多态性
【发布时间】:2021-01-22 14:30:48
【问题描述】:

有没有办法在 OCaml 中创建一个对整数和浮点数同样适用的多态添加函数?例如,如果我有这样的功能:

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

【问题讨论】:

  • 现在我对 OCaml 的了解确实非常有限,但即使您使用参数化类型,您仍然需要不同的运算符(++.)来构建部分总和,因为没有多态加法运算符,对吧?

标签: polymorphism ocaml


【解决方案1】:

编辑: 虽然这个答案具有理论价值,但您现在想阅读neo's answer

具有参数多态性,没有。 ad-hoc polymorphism,是的。

对于某些类型t,定义一个模块,

module type Semigroup = sig
  type t
  val add : t -> t -> t
end

还有一些实用函数,例如 partialsums,它们依赖于函子内部的 this,

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

您可以获得专门针对特定类型的partialsumst

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

这将允许定义一个泛型重载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 撰写,提供了一个很好的背景故事。

【讨论】:

  • 很好的答案! implicit module Semigroup_string 中的一个错字,我猜你的意思是 type t = string。此外,snd 出现在 Pervasives 中(不过,也许它出于教育目的而出现在答案中)。
  • @AntonTrunov:谢谢。我不知道Pervasives.snd 存在。
  • 模块化隐式还没有出现在 OCaml 中,不是吗? (截至 4.03)
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 这是迄今为止最好的答案,因为它依赖于现有的库和可用的语法。 :) 如果新手不清楚,Base 是 Jane Street 的替代标准库的名称。它需要使用包管理器,并且在理解参数化模块方面确实有一些额外的重量。但他们也围绕它建立了一本书,即2nd edition of Real World OCaml,它已经“进行中”了一段时间,但绝对不是空的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-04
    • 2015-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-17
    • 1970-01-01
    相关资源
    最近更新 更多