2015-01-05 2 views
10

Ich möchte einen DSL schaffen, in dem 2 (foo und bar) Funktionen nacheinander aufgerufen werden können, so dassWie eine typsichere DSL für verschachtelte Funktion erstellen, ruft

initialize() 
|> foo 10 
|> bar "A" 
|> foo 20 
|> bar "B" 
|> transform 

dies durch die Definition

ziemlich perfekt funktioniert
type FooResult = FooResult 
type BarResult = BarResult 

let foo param (result_type:BarResult, result) = (FooResult, transform param result) 
let bar param (result_type:FooResult, result) = (BarResult, transform param result) 

Nun aber ich möchte auch, dass mehrere erlauben bar Anrufe nacheinander ausgeführt werden können, jedoch foo immer noch nur einmal aufgerufen werden müssen

initialize() 
|> foo 10 
|> bar "A" 
//OK 
|> bar "B" 
|> transform 

initialize() 
|> foo 10 
|> bar "A" 
|> foo 20 
//should yield an compile error 
|> foo 30 
|> bar "B" 
|> transform 

In C# konnte ich bar überlasten, um entweder BarResult oder FooResult zu akzeptieren, aber das funktioniert nicht für F #. Zumindest nicht leicht. Ich habe auch versucht, einige diskriminierende Gewerkschaften zu schaffen, aber ich kann es wirklich nicht fassen.

Antwort

14

Das ist eine lustige Frage!

Ihr existierender Code funktioniert ganz gut, aber ich würde eine Änderung vornehmen - Sie müssen tatsächlich nicht tatsächliche FooResult und BarResult Werte weitergeben. Sie können eine Art MarkedType<'TPhantom, 'TValue> definieren, die von der anderen Art, die einen Wert von 'TValue mit einer speziellen „mark“ angegeben repräsentiert:

type MarkedValue<'TPhantom, 'TValue> = Value of 'TValue 

Dann können Sie Schnittstellen Typ Parameter für den Phantom-Typen verwenden. Ich fand es ein bisschen schwer über die „Ergebnisse“ zu denken, also werde ich Eingänge verwenden, anstatt:

type IFooInput = interface end 
type IBarInput = interface end 

Der Trick ist nun, dass Sie kann auch eine Schnittstelle definieren, die sowohl IFooInput und IBarInput:

type IFooOrBarInput = 
    inherit IFooInput 
    inherit IBarInput 

Also, alles, was Sie jetzt ot tun müssen, ist entsprechende Anmerkungen zu foo und bar hinzuzufügen:

let foo param (Value v : MarkedValue<#IFooInput, _>) : MarkedValue<IBarInput, _> = 
    Value 0 

let bar param (Value v : MarkedValue<#IBarInput, _>) : MarkedValue<IFooOrBarInput, _> = 
    Value 0 

Hier ist die Annotation auf der Eingabe besagt, dass es alles akzeptieren soll, was von IFooInput oder IBarInput ist oder erbt. Aber das Ergebnis der bar Funktion wird mit IFooOrBarInput markiert, was es möglich macht es sowohl passieren zu foo und bar:

(Value 0 : MarkedValue<IFooInput, _>) 
|> foo 10 
|> bar "A" 
|> bar "A" 
|> foo 20 
|> bar "B" 
+0

wow! Ich muss zugeben, dass ich nicht an Schnittstellen gedacht habe - aber selbst wenn ich es getan hätte, hätte ich sicherlich keine solche Lösung gefunden. Denken Sie, dass auch eine Lösung mit diskriminierenden Verbindungen möglich wäre? – robkuz

+1

Ich bin nicht sicher, dass DUs funktionieren würde - der Schlüsseltrick hier ist, die Vererbungsbeziehung zwischen Schnittstellen zu verwenden (wo zwei verschiedene Typen kompatibel sind). Ich denke, "Inline" und Überladung könnte eine Alternative sein. –

+0

Nur noch eine Frage. Was bedeutet der Hash in 'MarkedValue <#IBarInput, _>'? – robkuz

Verwandte Themen