2016-04-25 15 views
10

Ho molti programmi scritti in OCaml, alcuni usano i funtori. Ora, sto considerando di scrivere e riscrivere una parte di codice in F # (per beneficiare di alcuni vantaggi che OCaml non ha). Una cosa di cui ho paura è scrivere codice in F # per ciò che fanno i functors in OCaml.Come scrivere il codice in F # per ciò che fanno i funtori in OCaml?

Ad esempio, come è possibile emulare this example from OCaml manual in F #?

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 

risposta

6

Come avrete notato, F # non ha funtori - unità F # non può essere parametrizzato da tipi. È possibile ottenere risultati simili in F # utilizzando le parti orientate agli oggetti del linguaggio: interfacce, classi generiche ed ereditarietà.

Ecco un approccio ambiguo per emulare il tuo esempio.

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 
+0

Grazie per questa risposta dettagliata ... Quando si dice "approccio con mano pesante", vuoi dire che le persone nella pratica non codificano in questo modo? – SoftTimur

+1

@SoftTimur: Sort of - Ho provato a non divergere molto dal codice originale, e non mi è sembrato naturale quando ho passato il tipo 'Set'. Non è lontano, ma avrei potuto trovare qualcosa di diverso se partissi da un foglio pulito. – scrwtp

+1

@SoftTimur: forse sono solo i parametri di tipo contorto su 'Set'. – scrwtp

6

Ecco un approccio un po 'diverso che raggiunge lo stesso risultato utilizzando una classe generica e un oggetto per tipo.

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] 
+0

Ecco cosa intendevo quando ho chiamato la mia soluzione con mano pesante e non sentendomi naturale. – scrwtp

+0

Sarei completamente fuori, se ho menzionato https://msdn.microsoft.com/en-us/library/dd412070(v=vs.110).aspx, e aggiunto silenziosamente e attentamente: Perché scrivere codice quando la soluzione è disponibile ? ;-) (Sì, capisco il vantaggio di imparare facendo ...) –

+0

@HelgeReneUrholm La domanda era come portare il codice OCaml che utilizza i funtori su F #, non come implementare il set ordinato. Spero che questo esempio aiuti a capire come portare i casi del mondo reale a F #. – hvester