2015-01-05 60 views
10

我想創建一個DSL,其中2(foobar)功能可以連續叫,這樣如何創建交錯功能的類型安全的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仍然必須只有一次叫

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

這是一個有趣的問題!

您現有的代碼工作得非常好,但我會做一個更改 - 您實際上不需要傳遞實際的FooResultBarResult值。您可以定義類型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 

所以,你現在需要做的催產素是適當的添加註解foobar

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

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

在這裏,輸入的註釋說它應該接受從IFooInputIBarInput繼承的任何東西。但bar函數的結果都標有IFooOrBarInput,這使得它有可能將它傳遞給兩個foobar

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

哇!我必須承認我沒有考慮接口 - 但即使我做了,我當然也不會想出這樣的解決方案。你認爲歧視工會的解決方案也是可能的嗎? – robkuz

+1

我不確定DU是否可以工作 - 這裏的關鍵技巧是使用接口之間的繼承關係(兩種不同類型兼容)。我想'inline'和重載可能是一種選擇。 –

+0

還有一個問題。 'MarkedValue <#IBarInput,_>'中散列的含義是什麼? – robkuz