F# 在携带状态的同时绑定到输出
F# Binding to output while carrying the state
我正在尝试使用计算表达式来构建操作列表。我需要绑定到从 getFood
操作返回的值,以便我可以注册稍后的步骤来使用它。
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
但我不知道是什么。
(*
Example result
testPlan =
(GetFood Chicken,(
(Sleep 10,(
EatFood Chicken
))
))
*)
要完成这样的工作,您可能需要一种具有更多单子结构并允许您存储任何结果的类型,而不仅仅是计划。我会使用这样的东西:
type Step =
| GetFood of Food
| Eat of Food
| Sleep of duration:int
type Plan<'T> = Plan of Step list * 'T
现在,Plan<'T>
表示生成类型 'T
的值并沿途收集计划的计算。 GetFood
可以出方案,还return菜:
let getFood () =
printfn "GetFood"
let randomFood =
if rng.NextDouble() > 0.5 then Food.Chicken
else Food.Rice
Plan([GetFood randomFood], randomFood)
实施计算构建器有点像魔法,但您现在可以定义 Bind
和您的自定义操作。为了能够访问参数中的变量,它需要是一个函数,如 where
或 select
操作:
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()
这实际上可以让您实施测试计划:
let testPlan =
plan {
let! food = getFood ()
sleep 10
eat food
return ()
}
也就是说,在实践中,我不确定我是否真的想要使用它。我可能只会使用 seq { .. }
计算,使用 yield
来累积计划的步骤。
我正在尝试使用计算表达式来构建操作列表。我需要绑定到从 getFood
操作返回的值,以便我可以注册稍后的步骤来使用它。
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
但我不知道是什么。
(*
Example result
testPlan =
(GetFood Chicken,(
(Sleep 10,(
EatFood Chicken
))
))
*)
要完成这样的工作,您可能需要一种具有更多单子结构并允许您存储任何结果的类型,而不仅仅是计划。我会使用这样的东西:
type Step =
| GetFood of Food
| Eat of Food
| Sleep of duration:int
type Plan<'T> = Plan of Step list * 'T
现在,Plan<'T>
表示生成类型 'T
的值并沿途收集计划的计算。 GetFood
可以出方案,还return菜:
let getFood () =
printfn "GetFood"
let randomFood =
if rng.NextDouble() > 0.5 then Food.Chicken
else Food.Rice
Plan([GetFood randomFood], randomFood)
实施计算构建器有点像魔法,但您现在可以定义 Bind
和您的自定义操作。为了能够访问参数中的变量,它需要是一个函数,如 where
或 select
操作:
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()
这实际上可以让您实施测试计划:
let testPlan =
plan {
let! food = getFood ()
sleep 10
eat food
return ()
}
也就是说,在实践中,我不确定我是否真的想要使用它。我可能只会使用 seq { .. }
计算,使用 yield
来累积计划的步骤。