Reputation: 1716
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
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
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
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
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