2015-01-05 41 views
10

Chcę utworzyć DSL gdzie 2 (foo i bar) funkcje mogą być wywoływane z rzędu, dzięki czemuJak utworzyć DSL typesafe dla funkcji przeplatanej wzywa

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

to działa całkiem doskonały definiując

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) 

teraz jednak chcę, aby umożliwić również, że wiele bar połączenia mogą być wykonywane z rzędu jednak foo wciąż muszą być wywołana tylko raz

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 

W języku C# mogłem przeciążać bar, aby zaakceptować BarResult lub FooResult, ale to nie działa dla F #. Przynajmniej nie łatwo. Próbowałem też stworzyć związki dyskryminacyjne, ale naprawdę nie mogę się z tym pogodzić.

Odpowiedz

14

To zabawne pytanie!

Twój dotychczasowy kod działa całkiem nieźle, ale zrobiłbym jedną zmianę - nie musisz omijać rzeczywistych wartości FooResult i BarResult. Można zdefiniować typ MarkedType<'TPhantom, 'TValue> która reprezentuje wartość 'TValue ze specjalnym „znakiem” określonego przez innego rodzaju:

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

Następnie można użyć interfejsów jako parametry typu dla typu phantom. Uważam, że to trochę trudno myśleć o „Wyniki”, więc mam zamiar używać zamiast wejść:

type IFooInput = interface end 
type IBarInput = interface end 

Sztuką jest to, że teraz można również zdefiniować interfejs, który jest zarówno IFooInput i IBarInput:

type IFooOrBarInput = 
    inherit IFooInput 
    inherit IBarInput 

Tak, wszystko co musisz zrobić ot teraz jest dodanie odpowiednich adnotacji do foo i bar:

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

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

Tutaj adnotacja na wejściu mówi, że powinna akceptować wszystko, co jest lub dziedziczy po IFooInput lub IBarInput. Ale wynik funkcji bar oznaczona IFooOrBarInput, co pozwala przekazać ją zarówno foo i bar:

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

wow! Muszę przyznać, że nie myślałem w kategoriach interfejsów - ale nawet gdyby tak było, na pewno nie wymyśliłbym takiego rozwiązania. Czy uważasz, że możliwe byłoby również rozwiązanie z dyskryminującymi związkami? – robkuz

+1

Nie jestem pewien, czy DU zadziałają - kluczową tu metodą jest wykorzystanie relacji dziedziczenia między interfejsami (gdzie dwa różne typy są kompatybilne). Przypuszczam, że 'inline' i przeciążanie może być alternatywą. –

+0

Jeszcze jedno pytanie. Jakie jest znaczenie hasha w 'MarkedValue <#IBarInput, _>'? – robkuz