F# 将状态传递给 Bind 中的函数

F# passing state into a function in Bind

这是我在这里提出的问题 () 的演变。

我正在尝试获取 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,你可以看到 here。使用这个,我破解了你的代码如下:

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