4

I am trying to get my head around defining a stateful builder and I can't get around some compiler errors

type Movement =
    | Left of int
    | Right of int

type MovementState = Movement list -> Movement list

type MovementBuilder () =
    member x.Zero () : MovementState = id
    member __.Return x : MovementState = id
    member __.Bind(m: MovementState, f: MovementState ) = fun v -> f (m v)

    [<CustomOperation("left", MaintainsVariableSpaceUsingBind = true)>]
    member x.Left(ms, value) = x.Bind(ms, fun xs -> xs @ [Left value])

    [<CustomOperation("right", MaintainsVariableSpaceUsingBind = true)>]
    member x.Right(ms, value) = x.Bind(ms, fun xs -> xs @ [Right value])

let movement = MovementBuilder()

[]
|> movement {
    left 10
    right 20
}
|> printfn "list %A"
//prints [Left 10; Right 20]

However now I want introduce a let! or yield so I can add additional items without going via the defined CustomOperations so that for example I can so the following

[]
|> movement {
    left 10
    let! _ = (fun xs -> xs @ [Right 99])
    //I also tried naming the value
    //let! x = (fun xs -> xs @ [Right 99])
    //I also tried wrapping it into another function ...
    //let! x = fun () -> (fun xs -> xs @ [Right 99])
    right 20
}
|> printfn "list %A"
//Should print [Left 10; Right 99; Right 20]

Any help is greatly appreciated. Bonus Karma will be send for explaining how the compiler rewrites that into a series of Binds

Thx

robkuz
  • 9,488
  • 5
  • 29
  • 50
  • 1
    why don't you add the usual parameter like this: https://gist.github.com/CarstenKoenig/32701ed30577fc7128abd150c579cc01 ? - if you don't care about this part of the monad you can just use `Movement list` instead (you don't really do more) - just commenting because I don't really know what you are trying to do – Random Dev Jun 14 '17 at 09:51
  • Thanks for your example - can you get it to run with `yield` instead of `do!`? – robkuz Jun 14 '17 at 19:59
  • yes you can (I updated the gist) - but in this case I would not include `left`/`right` in the builder but just have them be functions – Random Dev Jun 14 '17 at 21:01

1 Answers1

3

You seem to have a monadic type here which cannot "contain" anything (i.e. Async<'a> can contain types of 'a).

This means that the only sensible type to be able to bind is unit, which makes the signature of bind member __.Bind(m: MovementState, f : unit -> MovementState).

This allows you to use do! notation to manipulate your list of movestates and will mean a bit of a rewrite of your left and right methods. I believe you will need a combine method on your builder as well, but the compiler will let you know if you do or not pretty quickly! let! notation doesn't make a great deal of sense here as you have no "contained" type to unwrap.

I have a short example of this in a blog post, most relevant code below:

type PTD = ProvidedTypeDefinition -> ProvidedTypeDefinition

type ProvidedTypeBuilder () =
    member __.Zero () : PTD =
        id
    member __.Return _ : PTD =
        id
    member __.Bind(m, f : unit -> PTD) =
        fun ptd -> (f ()) (m ptd)
    member x.Combine(m1 : PTD, m2 : PTD) : PTD =
        x.Bind(m1, fun () -> m2)

    [<CustomOperation("addMember", MaintainsVariableSpaceUsingBind = true)>]
    member x.AddMember(ptd, member') =
        let func =
          fun (instance : ProvidedTypeDefinition) ->
              instance.AddMember member'
              instance
        x.Bind(ptd, fun () -> func)

As an example of how you can use do! notation, you can do something like this rather than building custom operation:

let ptd = ProvidedTypeBuilder()

let test =
    ptd {
        addMember (ProvidedProperty("MyProp", typeof<string>))
        do! (fun ptd -> ptd.AddObsoleteAttribute("Hey, don't use this anymore"); ptd)
    }
mavnn
  • 9,101
  • 4
  • 34
  • 52
  • I know your blog post (thanks for that). However your example only uses custom operations and no combination of those with `yield` or `do!` and that is exactly what I am looking for – robkuz Jun 14 '17 at 10:47
  • @robkuz I've added an example of using do! notation with the CE I defined above. – mavnn Jun 14 '17 at 11:28
  • 1
    @robkuz just a Yield and probably YieldFrom member (Combine is already here) - I updated my gist and you can find details here: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions – Random Dev Jun 14 '17 at 21:02