2016-07-11 15 views
5

Ich arbeite seit ein paar Monaten mit F #, fand aber keine zufriedenstellende Lösung für mein Problem. Ich möchte eine Folge von Operationen als eine diskriminierte Vereinigung von Werten oder Operationen an diesen Werten beschreiben. Auf diese Weise mein Typ Val < ‚o> wird wie folgt definiert:F # generische Constraint Union Typ

type Val<'o> = 
    | Val of 'o 
    | Func1 of ('a->'o) * Val<'a> 
    | Func2 of ('a->'b->'o) * Val<'a> * Val<'b> 

Der Typ Val <‘ o> kann die Liste von rekursiv Anwendung aller Operationen und immer noch hält in einen ‚O-Typen umgewandelt werden von Operationen.

Aber ich kann die generischen Typen 'a und' b und ihre Nebenbedingungen nicht definieren, wenn ich Val < 'a,' b, 'o> nicht verwende. Wenn ich das tue, muss ich die Unter Val generische Typen definieren, die ich generic bleiben wollen:

type Val<'a, 'b, 'o> = 
    | Val of 'o 
    | Func1 of ('a->'o) * Val<?, ?, 'a> 
    | Func2 of ('a->'b->'o) * Val<?, ?, 'a> * Val<?, ?, 'b> 

Gibt es eine F # Struktur, die für dieses Problem angepasst werden könnte?

Vielen Dank

[Bearbeiten]

Zu meinem Problem weiter zu beschreiben, ich versuche, eine erschöpfende Darstellung einer FRP-Struktur zu erhalten (aber das Genericity Problem ist das gleiche für Ereignisse/Signale, die für Werte).
Die Darstellung könnte entweder serialisierten für die Speicherung in Datenbanken, übersetzt in Text für die Anzeige und Benutzer bearbeiten oder ausgewertet werden um ein Ergebnis zu erhalten:

"Func (x -> x²) (Val(3.4))" <--> representation <--> 11.56 
      | 
      user 

ich ganz gut einen Prototyp hergestellt arbeiten mit einem PrimitiveValue Union-Typ, und Funktionen Strings kompiliert zur Laufzeit in generische obj[] -> obj Funktionen, aber die Auswertung ist sehr schwer auf Typprüfung und Casting (vor allem, weil ich auch Arrays und Optionen in PrimitiveValue), also suchte ich nach einer eleganten und stark typisierten Lösung.

Antwort

6

Das grundlegende Problem hier ist, dass F # lässt Sie nicht sagen, dass die 'a und 'b im Fall der diskriminierten Union "Parameter" der Daten in dem Fall gespeichert sind. Einige andere Sprachen unterstützen dies (es heißt in Haskell verallgemeinerte algebraische Datentypen), aber es gibt einen üblichen Kompromiss, die Sprache komplizierter zu machen.

Sie können dies tatsächlich in F # emulieren, aber es ist hässlich - also würde ich zweimal überlegen, bevor Sie so gehen. Die Idee ist, dass Sie eine Schnittstelle mit einer generischen Methode definieren können, die mit den entsprechenden Typargumenten 'a und 'b aufgerufen wird.

type Val<'T> = 
    | Val of 'T 
    | Func of IFunc<'T> 

and IFunc<'T> = 
    abstract Invoke<'R> : IFuncOperation<'T, 'R> -> 'R 

and IFuncOperation<'T2, 'R> = 
    abstract Invoke<'T1> : ('T1 -> 'T2) * Val<'T1> -> 'R 

Der in Func gewickelten Wert kann IFuncOperation gegeben und es wird rufen Sie es mit Ihrem 'a die Art Argument der generischen Methode zu sein - die 'T1 in meiner Namensgebung.

Sie können Bau des Wertes recht schön machen:

let makeFunc f v = 
    Func({ new IFunc<_> with member x.Invoke(op) = op.Invoke(f, v) })  
let makeVal v = Val(v) 

let valString = makeFunc (fun n -> sprintf "Got: %d" n) (makeVal 42) 

Nun valString stellt eine int -> string zu Val<int> angewandt Transformation.

Der Code, den Sie schreiben müssen Pattern-Matching auf Func zu tun ist ziemlich obwohl hässlich:

let rec eval<'T> (value:Val<'T>) : 'T = 
    match value with 
    | Val r -> r 
    | Func f -> 
     { new IFuncOperation<'T, 'T> with 
      member x.Invoke<'S>(f, value:Val<'S>) = f (eval<'S> value) } 
     |> f.Invoke 

eval valString 

ich in einigen der Interna von Deedle ähnliches Muster verwendet haben, aber nie in Code, der einmal in der Nähe wäre, zu welchem ​​Zweck Benutzer schreiben würden. Ich denke, dass dies auf einer sehr gut versteckten internen Ebene akzeptabel ist, aber ich würde definitiv vermeiden, es in etwas zu verwenden, das häufig genannt wird.

Je nachdem, was Ihr ursprüngliches Problem war, gibt es wahrscheinlich eine schönere Möglichkeit - Sie könnten eine diskriminierte Union PrimitiveValue definieren, um die verschiedenen primitiven Werte zu halten, die Ihre Berechnung erzeugen kann, oder Sie könnten nur die Operationen mit einer Schnittstelle darstellen Es ist schwer zu sagen, was in Ihrem Fall besser ist, ohne den Kontext zu kennen.

+1

Beachten Sie, dass Sie möglicherweise auch eine zweite "Invoke" -Überladung verwenden möchten, um 'Einheit'-wiederkehrende Operationen zu ermöglichen. – kvb

+0

Beachten Sie auch, dass das Wickeln von ''T1 ->' T2 'durch eine andere Anwendung von' Val <_>' in 'IFuncOperation <_,_>' es Ihnen ermöglicht, Funktionen beliebiger Curry-Art auf eine entsprechende Anzahl von 'Val <_>'s auf eine nette Weise anzuwenden. – kvb