F#携带状态时绑定到输出 [英] F# Binding to output while carrying the state

查看:38
本文介绍了F#携带状态时绑定到输出的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用计算表达式来构建动作列表.我需要绑定一个从 getFood 操作返回的值,以便我可以注册一个后续步骤来使用它.

I am trying to use a Computation Expression to build a list of actions. I need to bind to a value that gets returned from the getFood action so that I can register a later step to consume it.

type Food =
    | Chicken
    | Rice

type Step =
    | GetFood of Food
    | Eat of Food
    | Sleep of duration:int

type Plan = Plan of Step list

type PlanBuilder () =

    member this.Bind (plan:Plan, f) =
        f plan
    member this.Yield _ = Plan []
    member this.Run (Plan x) = Plan (List.rev x)

    [<CustomOperation("eat")>]
    member this.Eat (Plan p, food) =
        printfn "Eat"
        Plan ((Eat food)::p)

    [<CustomOperation("sleep")>]
    member this.Sleep (Plan p, duration) =
        printfn "Sleep"
        Plan ((Sleep duration)::p)

let plan = PlanBuilder()

let rng = System.Random(123)


let getFood (Plan p) =
    printfn "GetFood"
    let randomFood = 
        if rng.NextDouble() > 0.5 then
            Food.Chicken
        else
            Food.Rice
    (Plan ((GetFood randomFood)::p)), randomFood

let testPlan =
    plan {
        let! food = getFood // <-- This is what I am trying to get to work
        sleep 10
        eat food
    }

我相信问题出在 Bind 上,但我不知道这是什么.

I believe the problem is with the Bind but I can't figure out what it is.

(*
Example result
testPlan =
    (GetFood Chicken,(
        (Sleep 10,(
            EatFood Chicken
        ))
    ))
*)

推荐答案

要使此工作正常进行,您可能需要一种具有更单子结构且可以存储任何结果的类型,而不仅仅是计划.我会用这样的东西:

To make something like this work, you will probably need a type that has a more monadic structure and lets you store any result, rather than just the plan. I would use something like this:

type Step =
  | GetFood of Food
  | Eat of Food
  | Sleep of duration:int

type Plan<'T> = Plan of Step list * 'T

现在, Plan<'T> 表示一种计算,该计算会产生类型为'T 的值,并沿途收集计划. GetFood 可以制定计划,但也可以返回食物:

Now, Plan<'T> represents a computation that produces a value of type 'T and collects a plan along the way. GetFood can produce a plan, but also return the food:

let getFood () =
  printfn "GetFood"
  let randomFood = 
    if rng.NextDouble() > 0.5 then Food.Chicken
    else Food.Rice
  Plan([GetFood randomFood], randomFood)

实施计算构建器有点荒唐,但是您现在可以定义 Bind 和您的自定义操作.为了能够访问参数中的变量,它必须是 where select 操作中的函数:

Implementing a computation builder is a bit of a black art, but you can now define Bind and your custom operations. To be able to access variables in the argument, it needs to be a function as in the where or select operations:

type PlanBuilder () =

  member this.For (Plan(steps1, res):Plan<'T>, f:'T -> Plan<'R>) : Plan<'R> =
    let (Plan(steps2, res2)) = f res
    Plan(steps1 @ steps2, res2)

  member this.Bind (Plan(steps1, res):Plan<'T>, f:'T -> Plan<'R>) : Plan<'R> =
      let (Plan(steps2, res2)) = f res
      Plan(steps1 @ steps2, res2)
  
  member this.Yield x = Plan([], x)
  member this.Return x = Plan([], x)
  member this.Run (Plan(p,r)) = Plan(List.rev p, r)

  [<CustomOperation("eat", MaintainsVariableSpace=true)>]
  member this.Eat (Plan(p, r), [<ProjectionParameter>] food) =
      Plan((Eat (food r))::p, r)

  [<CustomOperation("sleep", MaintainsVariableSpace=true)>]
  member this.Sleep (Plan(p, r), [<ProjectionParameter>] duration) =
      Plan ((Sleep (duration r))::p, r)

let plan = PlanBuilder()

这实际上使您可以实施测试计划:

This actually lets you implement your test plan:

let testPlan =
  plan {
      let! food = getFood () 
      sleep 10
      eat food
      return ()
  }

也就是说,实际上,我不确定我是否真的想使用它.我可能只是一个使用产量来积累计划步骤的 seq {..} 计算.

That said, in practice, I'm not sure I would actually want to use this. I would probably just us a seq { .. } computation that uses yield to accumulat the steps of the plan.

这篇关于F#携带状态时绑定到输出的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆