This is an evolution of the question I asked here (F# Binding to output while carrying the state).
I am trying to get the Bind
method to take a function with this signature PlanAccumulator<'a> -> PlanAccumulator<'b>
. The getFood
function is an example of this. I am wanting the Bind
method to call getFood
with the PlanAccumulator<'a>
object.
The challenge is getting the Computation Expression (CE) to call the getFood
function inside of the Bind
method using the PlanAccumulator
that exists at that point in the CE. I'm not sure how to do this but I feel like it should be possible.
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
}
Here's an example of what testPlan
would be if this worked as desired:
val testPlan : Step list =
[
(StepId 1, GetFood Chicken)
(StepId 2, Sleep 1)
(StepId 3, Eat Chicken)
]