I want to create a DSL where 2 (foo
and bar
) functions can be called in succession so that
initialize()
|> foo 10
|> bar "A"
|> foo 20
|> bar "B"
|> transform
this works pretty perfect by defining
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)
Now however I want to also allow that multiple bar
calls can be executed in succession however foo
s still have to be called only once
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
In C# I could overload bar
to either accept BarResult or FooResult but that doesnt work for F#. At least not easily.
I also tried to create some Discriminate Unions but I really cant get my head around it.
This is a fun question!
Your existing code works quite nicely, but I would do one change - you do not actually need to pass around actual FooResult
and BarResult
values. You can define a type MarkedType<'TPhantom, 'TValue>
which represents a value of 'TValue
with a special "mark" specified by the other type:
type MarkedValue<'TPhantom, 'TValue> = Value of 'TValue
Then you can use interfaces as type parameters for the phantom type. I found it a bit hard to think about the "results", so I'm going to use inputs instead:
type IFooInput = interface end
type IBarInput = interface end
The trick now is that you can also define an interface that is both IFooInput
and IBarInput
:
type IFooOrBarInput =
inherit IFooInput
inherit IBarInput
So, all you need ot do now is to add appropriate annotations to foo
and bar
:
let foo param (Value v : MarkedValue<#IFooInput, _>) : MarkedValue<IBarInput, _> =
Value 0
let bar param (Value v : MarkedValue<#IBarInput, _>) : MarkedValue<IFooOrBarInput, _> =
Value 0
Here, the annotation on the input says that it should accept anything that is or inherits from IFooInput
or IBarInput
. But the result of the bar
function is marked with IFooOrBarInput
, which makes it possible to pass it to both foo
and bar
:
(Value 0 : MarkedValue<IFooInput, _>)
|> foo 10
|> bar "A"
|> bar "A"
|> foo 20
|> bar "B"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With