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 foos 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