Skip to content

Instantly share code, notes, and snippets.

@Krzysztof-Cieslak
Created November 19, 2019 15:36
Show Gist options
  • Save Krzysztof-Cieslak/746b813ac3020d685cc1446354bf404c to your computer and use it in GitHub Desktop.
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#
//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
}
}
@isaacabraham
Copy link

@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