这是我在这里问的问题的演变(F#在携带状态时绑定到输出(。
我正在尝试让Bind
方法接受具有此签名PlanAccumulator<'a> -> PlanAccumulator<'b>
的函数。getFood
函数就是一个例子。我希望Bind
方法用PlanAccumulator<'a>
对象调用getFood
。
挑战在于让计算表达式(CE(使用CE中存在的PlanAccumulator
在Bind
方法内部调用getFood
函数。我不知道该怎么做,但我觉得这应该是可能的。
type StepId = StepId of int
type State = {
LastStepId : StepId
}
type Food =
| Chicken
| Rice
type Step =
| GetFood of StepId * Food
| Eat of StepId * Food
| Sleep of StepId * duration:int
type PlanAccumulator<'T> = PlanAccumulator of State * Step list * 'T
let rng = System.Random(123)
let getFood (PlanAccumulator (s, p, r)) =
printfn "GetFood"
let randomFood =
if rng.NextDouble() > 0.5 then Food.Chicken
else Food.Rice
let (StepId lastStepId) = s.LastStepId
let nextStepId = StepId (lastStepId + 1)
let newState = { s with LastStepId = nextStepId }
let newStep = GetFood (nextStepId, randomFood)
PlanAccumulator (newState, newStep::p, randomFood)
type PlanBuilder (state: State) =
member this.For (PlanAccumulator (state, steps1, res):PlanAccumulator<'T>, f:'T -> PlanAccumulator<'R>) : PlanAccumulator<'R> =
printfn "For"
let (PlanAccumulator(state2, steps2, res2)) = f res
PlanAccumulator (state2, steps2 @ steps1, res2)
member this.Bind (input:PlanAccumulator<'a> -> PlanAccumulator<'T>, f:'T -> PlanAccumulator<'R>) : PlanAccumulator<'R> =
printfn "Bind"
// THIS IS THE PROBLEM: How do I get the previous PlanAccumulator to
// this point in the computation?
let PlanAccumulator (state1, steps1, res) = input previousAccumulator
let (PlanAccumulator(state2, steps2, res2)) = f (state1 res)
PlanAccumulator (state2, steps2 @ steps1, res2)
member this.Yield x =
printfn "Yield"
PlanAccumulator (state, [], x)
member this.Run (PlanAccumulator (s, p, r)) =
printfn "Run"
s, List.rev p
[<CustomOperation("eat", MaintainsVariableSpace=true)>]
member this.Eat (PlanAccumulator(s, p, r), [<ProjectionParameter>] food) =
printfn $"Eat: {food}"
let (StepId lastStepId) = s.LastStepId
let nextStepId = StepId (lastStepId + 1)
let newState = { s with LastStepId = nextStepId }
let newStep = Eat (nextStepId, (food r))
PlanAccumulator (newState, newStep::p, r)
[<CustomOperation("sleep", MaintainsVariableSpace=true)>]
member this.Sleep (PlanAccumulator (s, p, r), [<ProjectionParameter>] duration) =
printfn $"Sleep: {duration}"
let (StepId lastStepId) = s.LastStepId
let nextStepId = StepId (lastStepId + 1)
let newState = { s with LastStepId = nextStepId }
let newStep = Sleep (nextStepId, (duration r))
PlanAccumulator (newState, newStep::p, r)
// let plan = PlanBuilder()
let initialState = {
LastStepId = StepId 0
}
let newState, testPlan =
PlanBuilder initialState {
let! food = getFood
sleep 5
eat Chicken
}
以下是一个testPlan
的例子,如果它按要求工作的话:
val testPlan : Step list =
[
(StepId 1, GetFood Chicken)
(StepId 2, Sleep 1)
(StepId 3, Eat Chicken)
]
我想您想要一个普通的旧状态monad,您可以在这里看到。使用这个,我黑了你的代码如下:
type State<'s, 'a> = State of ('s -> ('a * 's))
module State =
let inline run state x = let (State(f)) = x in f state
let get = State(fun s -> s, s)
let put newState = State(fun _ -> (), newState)
let map f s = State(fun (state: 's) ->
let x, state = run state s
f x, state)
/// The state monad passes around an explicit internal state that can be
/// updated along the way. It enables the appearance of mutability in a purely
/// functional context by hiding away the state when used with its proper operators
/// (in StateBuilder()). In other words, you implicitly pass around an implicit
/// state that gets transformed along its journey through pipelined code.
type StateBuilder() =
member this.Zero () = State(fun s -> (), s)
member this.Return x = State(fun s -> x, s)
member inline this.ReturnFrom (x: State<'s, 'a>) = x
member this.Bind (x, f) : State<'s, 'b> =
State(fun state ->
let (result: 'a), state = State.run state x
State.run state (f result))
member this.Combine (x1: State<'s, 'a>, x2: State<'s, 'b>) =
State(fun state ->
let result, state = State.run state x1
State.run state x2)
member this.Delay f : State<'s, 'a> = f ()
member this.For (seq, (f: 'a -> State<'s, 'b>)) =
seq
|> Seq.map f
|> Seq.reduceBack (fun x1 x2 -> this.Combine (x1, x2))
member this.While (f, x) =
if f () then this.Combine (x, this.While (f, x))
else this.Zero ()
let state = new StateBuilder()
type StepId = StepId of int
type PlanState = {
LastStepId : StepId
}
type Food =
| Chicken
| Rice
type Step =
| GetFood of StepId * Food
| Eat of StepId * Food
| Sleep of StepId * duration:int
type PlanAccumulator = PlanAccumulator of PlanState * Step list
let rng = System.Random(123)
let getFood =
state {
printfn "GetFood"
let randomFood =
if rng.NextDouble() > 0.5 then Food.Chicken
else Food.Rice
let! (PlanAccumulator (planState, steps)) = State.get
let (StepId lastStepId) = planState.LastStepId
let nextStepId = StepId (lastStepId + 1)
let newState = { planState with LastStepId = nextStepId }
let newStep = GetFood (nextStepId, randomFood)
do! State.put (PlanAccumulator (newState, newStep :: steps))
return randomFood
}
let eat food =
state {
printfn "Eat: %A" food
let! (PlanAccumulator (planState, steps)) = State.get
let (StepId lastStepId) = planState.LastStepId
let nextStepId = StepId (lastStepId + 1)
let newState = { planState with LastStepId = nextStepId }
let newStep = Eat (nextStepId, food)
do! State.put (PlanAccumulator (newState, newStep :: steps))
}
let sleep duration =
state {
printfn "Sleep: %A" duration
let! (PlanAccumulator (planState, steps)) = State.get
let (StepId lastStepId) = planState.LastStepId
let nextStepId = StepId (lastStepId + 1)
let newState = { planState with LastStepId = nextStepId }
let newStep = Sleep (nextStepId, duration)
do! State.put (PlanAccumulator (newState, newStep :: steps))
}
let initialState = {
LastStepId = StepId 0
}
let initialPlan =
PlanAccumulator (initialState, List.empty)
[<EntryPoint>]
let main argv =
let _, testPlan =
state {
let! food = getFood
do! sleep 10
do! eat food
} |> State.run initialPlan
printfn "%A" testPlan
0
输出为:
GetFood
Sleep: 10
Eat: Chicken
PlanAccumulator
({ LastStepId = StepId 3 },
[Eat (StepId 3, Chicken); Sleep (StepId 2, 10); GetFood (StepId 1, Chicken)])