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 Bind
s
Thx
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)
}