如何为交错函数调用创建类型安全的 DSL
How to create a typesafe DSL for interleaved function calls
我想创建一个可以连续调用 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
仍然只需要调用一次
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# 不起作用。至少不容易。
我也尝试创建一些 Discriminate Unions 但我真的无法理解它。
这是一个有趣的问题!
您现有的代码工作得很好,但我会做一个更改 - 您实际上不需要传递实际的 FooResult
和 BarResult
值。您可以定义一个 MarkedType<'TPhantom, 'TValue>
类型,它表示 'TValue
的值,并具有由其他类型指定的特殊 "mark":
type MarkedValue<'TPhantom, 'TValue> = Value of 'TValue
然后你可以使用接口作为幻像类型的类型参数。我发现考虑 "results" 有点困难,所以我打算改用输入:
type IFooInput = interface end
type IBarInput = interface end
现在的诀窍是你还可以定义一个既是IFooInput
又是IBarInput
的接口:
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
或继承自 IFooInput
的内容。但是bar
函数的结果被标记为IFooOrBarInput
,这使得它可以同时传递给foo
和bar
:
(Value 0 : MarkedValue<IFooInput, _>)
|> foo 10
|> bar "A"
|> bar "A"
|> foo 20
|> bar "B"
我想创建一个可以连续调用 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
仍然只需要调用一次
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# 不起作用。至少不容易。
我也尝试创建一些 Discriminate Unions 但我真的无法理解它。
这是一个有趣的问题!
您现有的代码工作得很好,但我会做一个更改 - 您实际上不需要传递实际的 FooResult
和 BarResult
值。您可以定义一个 MarkedType<'TPhantom, 'TValue>
类型,它表示 'TValue
的值,并具有由其他类型指定的特殊 "mark":
type MarkedValue<'TPhantom, 'TValue> = Value of 'TValue
然后你可以使用接口作为幻像类型的类型参数。我发现考虑 "results" 有点困难,所以我打算改用输入:
type IFooInput = interface end
type IBarInput = interface end
现在的诀窍是你还可以定义一个既是IFooInput
又是IBarInput
的接口:
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
或继承自 IFooInput
的内容。但是bar
函数的结果被标记为IFooOrBarInput
,这使得它可以同时传递给foo
和bar
:
(Value 0 : MarkedValue<IFooInput, _>)
|> foo 10
|> bar "A"
|> bar "A"
|> foo 20
|> bar "B"