Created
November 19, 2019 15:36
-
-
Save Krzysztof-Cieslak/746b813ac3020d685cc1446354bf404c to your computer and use it in GitHub Desktop.
Using F# 4.7 implicit yield feature to implement DSL in F#
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Builder represeting only one entity in DSL | |
type Child1State = {SomeState : string} | |
type Child1Builder () = | |
[<CustomOperation("set_state")>] | |
member __.SetState (st, x) = {st with SomeState = x} | |
member __.Yield _ = {SomeState = ""} | |
let child = Child1Builder () | |
//Builder represeting another entity in DSL | |
type Child2State = {DifferentState : string; MoreState: int} | |
type Child2Builder () = | |
[<CustomOperation("set_different_state")>] | |
member __.SetDifferent (st, x) = {st with DifferentState = x} | |
[<CustomOperation("set_more_state")>] | |
member __.SetOther (st, x) = {st with MoreState = x} | |
member __.Yield _ = {DifferentState = ""; MoreState = 0} | |
let differentChild = Child2Builder () | |
//Another builder representing part of DSL | |
type NameBuilderState = {State: string} | |
type NameBuilder () = | |
member __.Yield (str: string) = {State = str} | |
member __.Zero () = {State = ""} | |
member __.Delay f = f() | |
member __.Combine(st, ns) = ns | |
let name = NameBuilder () | |
//Top level builder, that has it's own state, and embeds child entities | |
type MainBuilderState = {Name : string option; ChildState : Child1State option; OtherChildState: Child2State option } | |
type MainBuilder () = | |
member __.Yield (es: Child1State) = | |
{Name = None; ChildState = Some es; OtherChildState = None} | |
member __.Yield (es: Child2State) = | |
{Name = None; ChildState = None; OtherChildState = Some es} | |
member __.Yield (es: NameBuilderState) = | |
{Name = Some es.State; ChildState = None; OtherChildState = None} | |
member __.Yield (_: unit) = | |
{Name = None; ChildState = None; OtherChildState = None} | |
member __.Combine(st: MainBuilderState, ns: MainBuilderState) = | |
let ms = st.Name |> Option.orElse ns.Name | |
let c1 = st.ChildState |> Option.orElse ns.ChildState | |
let c2 = st.OtherChildState |> Option.orElse ns.OtherChildState | |
{Name = ms; ChildState = c1; OtherChildState = c2} | |
// This doesn't work, no idea why? | |
// [<CustomOperation("nnn")>] | |
// member __.Name (st, x) = {st with Name = Some x} | |
member __.Delay f = f() | |
member __.Zero () = {Name = None; ChildState = None; OtherChildState = None} | |
let main = MainBuilder () | |
//Final sample | |
let sample = main { | |
name {"My name" } | |
child { | |
set_state "Test" | |
} | |
differentChild { | |
set_different_state "Other test" | |
set_more_state 123 | |
} | |
} |
@baronfel one thing I've noticed is Combine doesn't seem to get called after a custom operation gets called e.g.
let sample = main {
nnn "ISAAC"
differentChild {
set_different_state "Other test"
set_more_state 123
}
}
creates the following object
{ Name = None
ChildState = None
OtherChildState = Some { DifferentState = "Other test"
MoreState = 123 } }
If you put nnn
at the end of the computation expression, it then works.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@baronfel I have no idea what that syntax means but yes, it works!