如何在 F# 中为仿函数在 OCaml 中的作用编写代码?

How to write code in F# for what functors do in OCaml?

我有许多用 OCaml 编写的程序,其中一些程序使用仿函数。现在,我正在考虑用 F# 编写和重写部分代码(以利用 OCaml 没有的一些优势)。我害怕的一件事是在 F# 中为仿函数在 OCaml 中所做的事情编写代码。

例如,我们如何在 F# 中模拟 this example from OCaml manual

type comparison = Less | Equal | Greater

module type ORDERED_TYPE = sig
  type t
  val compare: t -> t -> comparison
end

module Set =
functor (Elt: ORDERED_TYPE) -> struct
    type element = Elt.t
    type set = element list
    let empty = []
    let rec add x s =
      match s with
        [] -> [x]
      | hd::tl ->
         match Elt.compare x hd with
           Equal   -> s         (* x is already in s *)
         | Less    -> x :: s    (* x is smaller than all elements of s *)
         | Greater -> hd :: add x tl
  end

module OrderedString = struct
  type t = string
  let compare x y = if x = y then Equal else if x < y then Less else Greater
end

module OrderedInt = struct
  type t = int
  let compare x y = if x = y then Equal else if x < y then Less else Greater
end

module StringSet = Set(OrderedString)
module IntSet = Set(OrderedInt)

let try1 () = StringSet.add "foo" StringSet.empty
let try2 () = IntSet.add 2 IntSet.empty

如您所见,F# 没有函子 - F# 模块不能按类型参数化。您可以在 F# 中使用语言的面向对象部分 - 接口、泛型 类 和继承获得类似的结果。

这是模拟您的示例的一种粗暴方法。

type Comparison = Less | Equal | Greater

/// Interface corresponding to ORDERED_TYPE signature
type IOrderedType<'a> = 
    abstract Value: 'a
    abstract Compare: IOrderedType<'a> -> Comparison

/// Type that implements ORDERED_TYPE signature, different instantiations
/// of this type correspond to your OrderedInt/OrderedString modules.
/// The 't: comparison constraint comes from the fact that (<) operator 
/// is used in the body of Compare.
type Ordered<'t when 't: comparison> (t: 't) =
    interface IOrderedType<'t> with
        member this.Value = t
        member this.Compare (other: IOrderedType<'t>) = 
            if t = other.Value then Equal else if t < other.Value then Less else Greater

/// A generic type that works over instances of IOrderedType interface.
type Set<'t, 'ot when 't: comparison and 'ot :> IOrderedType<'t>> (coll: IOrderedType<'t> list) =

    member this.Values = 
        coll |> List.map (fun x -> x.Value)

    member this.Add(x: 't) = 
        let rec add (x: IOrderedType<'t>) s = 
            match coll with
            | [] -> [x]
            | hd::tl ->
                match x.Compare(hd) with
                | Equal   -> s         (* x is already in s *)
                | Less    -> x :: s    (* x is smaller than all elements of s *)
                | Greater -> hd :: add x tl
        Set<'t, 'ot>(add (Ordered(x)) coll)

    static member Empty = Set<'t, 'ot>(List.empty)

/// A helper function for Set.Add. Useful in pipelines. 
module Set =     
    let add x (s: Set<_,_>) =
       s.Add(x)

/// Type aliases for different instantiations of Set 
/// (these could have easily been subtypes of Set as well)
type StringSet = Set<string, Ordered<string>>
type IntSet = Set<int, Ordered<int>>

let try1 () = Set.add "foo" StringSet.Empty
let try2 () = Set.add 2 IntSet.Empty

try1().Values
try2().Values

这里有一个有点不同的方法,它使用通用 class 和每个类型一个对象来实现相同的结果。

type Comparison = Less | Equal | Greater

type Set<'a>(compare : 'a -> 'a -> Comparison) =

    member this.Empty : 'a list = []

    member this.Add x s = 
         match s with
         | [] -> [x]
         | hd::tl ->
             match compare x hd with
             | Equal   -> s         (* x is already in s *)
             | Less    -> x :: s    (* x is smaller than all elements of s *)
             | Greater -> hd :: this.Add x tl


let compare x y = if x = y then Equal else if x < y then Less else Greater

let compareFloats (x : float) (y : float) = if x = y then Equal else if x < y then Less else Greater

// Note that same generic compare function can be used for stringSet and intSet
// as long as the type parameter is explicitly given
let stringSet = Set<string>(compare)
let intSet = Set<int>(compare)

// Type parameter not needed, because compareFloats is not generic
let floatSet = Set(compareFloats)

let try1 () = stringSet.Add "foo" stringSet.Empty   // -> ["foo"]
let try2 () = intSet.Add 2 intSet.Empty             // -> [2]
let try3 () = floatSet.Add 3.0 floatSet.Empty       // -> [3.0]

F# 中的函数式方式主要依赖于类型推断,避免像 interface 这样的 OOP 结构或具有 member.

的类型
type Comparison = Less | Equal | Greater
type OrderedSet<'t> = 't list     // type alias, not really necessary

module OrderedSet =
    let empty                      : OrderedSet<_> = List.empty  // just an empty list
    let values (s : OrderedSet<_>) : OrderedSet<_> = s           // identity function
    let add compare x (s : OrderedSet<_>)  : OrderedSet<_> =
        let rec addR s =
            match s with
            | [] -> [x]
            | hd::tl ->
                match compare x hd with
                | Equal   -> s         (* x is already in s *)
                | Less    -> x :: s    (* x is smaller than all elements of s *)
                | Greater -> hd :: addR tl
        addR s

    let compare        x          y = if x = y then Equal else if x < y then Less else Greater
    let compareFloats (x : float) y = if x = y then Equal else if x < y then Less else Greater

    let addGeneric v = add compare       v
    let addFloat   v = add compareFloats v

并且是这样使用的:

let try1 () = OrderedSet.addGeneric "foo" OrderedSet.empty |> OrderedSet.addGeneric "bar"
let try2 () = OrderedSet.addGeneric 2     OrderedSet.empty |> OrderedSet.addGeneric 3 
let try3 () = OrderedSet.empty 
              |> OrderedSet.addFloat 3.0 
              |> OrderedSet.addFloat 1.0 
              |> OrderedSet.addFloat 2.0

try1() |> printfn "%A"  // OrderedSet<string> = ["bar"; "foo"]  
try2() |> printfn "%A"  // OrderedSet<int>    = [2; 3]
try3() |> printfn "%A"  // OrderedSet<float>  = [1.0; 2.0; 3.0]

类型别名 type OrderedSet<'t> = 't list 和函数 emptyvalues 并不是真正必要的,但它们有助于掩盖实际实现(如果需要的话)。