如何将前向管道表达式转换为计算表达式?

时间:2013-10-15 05:22:08

标签: f# monads computation-expression

所以,我想构建一个自定义计算表达式,允许我将其转换为

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 })

这样的事情 -

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

这是可能的,还是接近可能的?如果是这样,可以采取什么方法?这是一个monad还是更小的东西?

4 个答案:

答案 0 :(得分:6)

也许我应该回去做这个 -

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 }

它调试很好(您可以在Autos窗口中找到所有以前版本的tw_,并且可以对每个操作进行调试),而且我认为它不是太冗长。

答案 1 :(得分:2)

如果您使用 ExtCore 库(在NuGet上可用),它会提供一个名为tap的运算符,专门用于帮助调试流水线表达式。您可以通过“点击”管道来使用它,它将一个动作函数(返回unit)应用于管道中某个点的值,然后传递该值,以便它按预期继续流经管道。 / p>

例如:

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 })

相关函数tapAssert可用于将调试断言插入管道。

答案 2 :(得分:2)

State或Writer Monad可能有用,但是如果你想使用调试器忘记Monads,那就更糟了。 您可以做的一件事是重新定义管道运算符,如下所示:

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

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

你会看到:

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

或者您可以代替打印,将结果累积到可变的对象列表中。

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>]

这几乎就像作家monad。

但是如果你肯定想要使用调试器(它不是我最喜欢的工具),那么使用let的解决方案很好,但是你必须改变你的代码并且你放松了管道,在这种情况下它可能会最好在(|>)函数的主体中添加断点。

答案 3 :(得分:1)

你需要一个let!来传播monadic状态。

所以我猜你已经拥有了monad:它是普通的let,它接受​​当前的绑定,添加一个新的,并在后续计算中传递它!