2015-01-05 1 views
10

Я хочу создать DSL, где 2 (foo и bar) функции могут вызываться последовательно, так чтоКак создать типизированный DSL для чередования вызовов функций

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

это работает довольно совершенный путь определения

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) 

однако теперь я хочу также позволить, что мультипликатор bar вызовы могут выполняться последовательно, однако foo s все еще должны вызываться только один раз

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 

В C# я могу перегрузить bar, чтобы принять BarResult или FooResult, но это не работает для F #. По крайней мере, не легко. Я также пытался создать несколько Дискриминационных Союзов, но я действительно не могу опустить голову.

ответ

14

Это интересный вопрос!

Ваш существующий код работает очень хорошо, но я бы сделал одно изменение - вам фактически не нужно передавать фактические значения FooResult и BarResult. Вы можете определить тип MarkedType<'TPhantom, 'TValue>, который представляет собой значение 'TValue со специальным «знаком», указанного другим типом:

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

Затем вы можете использовать интерфейсы в качестве параметров типа для типа фантома. Я нашел это немного трудно думать о «результатах», поэтому я буду использовать входы вместо:

type IFooInput = interface end 
type IBarInput = interface end 

Хитрость теперь, что вы также можете определить интерфейс, который является одновременно и IFooInputIBarInput:

type IFooOrBarInput = 
    inherit IFooInput 
    inherit IBarInput 

Таким образом, все, что вам нужно ВЗ сделать сейчас, чтобы добавить соответствующие аннотации к foo и bar:

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

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

Здесь аннотация на входе говорит, что он должен принимать все, что есть или наследуется от IFooInput или IBarInput. Но результат функции bar отмечен IFooOrBarInput, что позволяет передать его как foo и bar:

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

вау! Должен признаться, я не думал с точки зрения интерфейсов, но даже если бы я это сделал, я бы точно не нашел такого решения. Считаете ли вы, что решение с дискриминационными союзами также возможно? – robkuz

+1

Я не уверен, что DUs будут работать - основной трюк здесь заключается в использовании отношения наследования между интерфейсами (где два разных типа совместимы). Я полагаю, что «inline» и перегрузка могут быть альтернативой. –

+0

Просто еще один вопрос. В чем смысл хеша в «MarkedValue <#IBarInput, _>'? – robkuz