Bryan Edds
Bryan Edds

Reputation: 1716

How do I turn a forward pipe expression into a computation expression?

So, I want to build a custom computation expression that would allow me to turn this -

testWorld |>
    subscribe ClickTestButtonAddress [] addBoxes |>
    addScreen testScreen TestScreenAddress |>
    setP (Some TestScreenAddress) World.optActiveScreenAddress |>
    addGroup testGroup TestGroupAddress |>
    addEntityGuiLabel (testLabelGuiEntity, testLabelGui, testLabel) TestLabelAddress |>
    addEntityGuiButton (testButtonGuiEntity, testButtonGui, testButton) TestButtonAddress |>
    addEntityActorBlock (testFloorActorEntity, testFloorActor, testFloor) TestFloorAddress |>
    (let hintRenderingPackageUse = HintRenderingPackageUse { FileName = "AssetGraph.xml"; PackageName = "Misc"; HRPU = () }
     fun world -> { world with RenderMessages = hintRenderingPackageUse :: world.RenderMessages }) |>
    (let hintAudioPackageUse = HintAudioPackageUse { FileName = "AssetGraph.xml"; PackageName = "Misc"; HAPU = () }
     fun world -> { world with AudioMessages = hintAudioPackageUse :: world.AudioMessages })

into something like this -

fwd {
    do! subscribe ClickTestButtonAddress [] addBoxes
    do! addScreen testScreen TestScreenAddress
    do! setP (Some TestScreenAddress) World.optActiveScreenAddress
    do! addGroup testGroup TestGroupAddress
    do! addEntityGuiLabel (testLabelGuiEntity, testLabelGui, testLabel) TestLabelAddress
    do! addEntityGuiButton (testButtonGuiEntity, testButtonGui, testButton) TestButtonAddress
    do! addEntityActorBlock (testFloorActorEntity, testFloorActor, testFloor) TestFloorAddress
    let hintRenderingPackageUse = HintRenderingPackageUse { FileName = "AssetGraph.xml"; PackageName = "Misc"; HRPU = () }
    do! fun world -> { world with RenderMessages = hintRenderingPackageUse :: world.RenderMessages }
    let hintAudioPackageUse = HintAudioPackageUse { FileName = "AssetGraph.xml"; PackageName = "Misc"; HAPU = () }
    do! fun world -> { world with AudioMessages = hintAudioPackageUse :: world.AudioMessages }}
    <| runFwd testWorld

Is this possible, or close to something possible? If so, what approach could be taken? Is this a monad, or something lesser?

Upvotes: 1

Views: 425

Answers (4)

Gus
Gus

Reputation: 26204

The State or the Writer Monad might be useful but if you want to use the debugger forget about Monads, it's even worse. One thing you can do is redefine the pipeline operator like this:

let (|>) a b = printfn "Value is: %A" a; b a

5 
    |> ((+) 40) 
    |> string 
    |> Seq.singleton  
    |> Seq.toArray

You'll see:

Value is: 5
Value is: 45
Value is: "45"
Value is: seq ["45"]
val it : string [] = [|"45"|]

Or you can instead of printing, accumulate results in a mutable list of objects.

let mutable results = [] : obj list
let (|>) a b = 
    results <- results @ [box a] // or set a breakpoint here
    b a  
...
val mutable results : obj list = [5; 45; "45"; <seq>]

This is almost like the writer monad.

But if you definitely want to use the debugger (it's not my favorite tool) you're solution with let is fine though you have to change your code and you loose the pipe forward, In that case it might be better to add a breakpoint in the body of the (|>) function.

Upvotes: 2

Bryan Edds
Bryan Edds

Reputation: 1716

Maybe I should just go back to doing this -

let tw_ = testWorld
let tw_ = subscribe ClickTestButtonAddress [] addBoxes tw_
let tw_ = addScreen testScreen TestScreenAddress tw_
let tw_ = setP (Some TestScreenAddress) World.optActiveScreenAddress tw_
let tw_ = addGroup testGroup TestGroupAddress tw_
let tw_ = addEntityGuiLabel (testLabelGuiEntity, testLabelGui, testLabel) TestLabelAddress tw_
let tw_ = addEntityGuiButton (testButtonGuiEntity, testButtonGui, testButton) TestButtonAddress tw_
let tw_ = addEntityActorBlock (testFloorActorEntity, testFloorActor, testFloor) TestFloorAddress tw_
let tw_ = { tw_ with RenderMessages = hintRenderingPackageUse :: tw_.RenderMessages }
{ tw_ with AudioMessages = hintAudioPackageUse :: tw_.AudioMessages }

It debugs fine (you can find all previous versions of tw_ in the Autos window and can step debug on each operation), and it's not too verbose I suppose.

Upvotes: 6

Jack P.
Jack P.

Reputation: 11525

If you use the ExtCore library (available on NuGet), it provides an operator called tap which is specifically provided to help debug pipelined expressions. You use it by "tapping" your pipeline, which applies an action function (returning unit) to the value at some point in the pipeline, then the value is passed through so it keeps flowing through the pipeline as expected.

For example:

testWorld
|> subscribe ClickTestButtonAddress [] addBoxes
|> addScreen testScreen TestScreenAddress
// Check to see if the screen was added correctly
|> tap (fun world ->
    // TODO : Insert code to check if the screen was added.
    // Or, put some dummy code here so you can set a breakpoint
    // on it to inspect 'world' in the debugger.
    ())
|> setP (Some TestScreenAddress) World.optActiveScreenAddress
|> addGroup testGroup TestGroupAddress
|> addEntityGuiLabel (testLabelGuiEntity, testLabelGui, testLabel) TestLabelAddress
|> addEntityGuiButton (testButtonGuiEntity, testButtonGui, testButton) TestButtonAddress
|> addEntityActorBlock (testFloorActorEntity, testFloorActor, testFloor) TestFloorAddress
|> (let hintRenderingPackageUse = HintRenderingPackageUse { FileName = "AssetGraph.xml"; PackageName = "Misc"; HRPU = () }
    fun world -> { world with RenderMessages = hintRenderingPackageUse :: world.RenderMessages }) 
|> (let hintAudioPackageUse = HintAudioPackageUse { FileName = "AssetGraph.xml"; PackageName = "Misc"; HAPU = () }
    fun world -> { world with AudioMessages = hintAudioPackageUse :: world.AudioMessages })

A related function, tapAssert, can be used to insert debugging assertions into your pipeline.

Upvotes: 2

nicolas
nicolas

Reputation: 9835

You'd need a let! to propagate the monadic state.

So I guess you have the monad already : it is the plain old let which takes the current bindings, add a new one, and pass it on subsequent computations !

Upvotes: 1

Related Questions