如何为交错函数调用创建类型安全的DSL

时间:2015-01-05 16:29:40

标签: f# dsl

我想创建一个DSL,其中可以连续调用2个(foobar)函数,以便

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#不起作用。至少不容易。 我也试图建立一些判别联盟,但我真的无法理解它。

1 个答案:

答案 0 :(得分:14)

这是一个有趣的问题!

您现有的代码工作得非常好,但我会做一个更改 - 您实际上并不需要传递实际的FooResultBarResult值。您可以定义一个类型MarkedType<'TPhantom, 'TValue>,它代表'TValue的值,带有特殊的&#34;标记&#34;由另一种类型指定:

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

然后您可以使用接口作为幻像类型的类型参数。我觉得有点难以考虑&#34;结果&#34;所以我将改用输入:

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"