Recursively update a State Monad

180 views Asked by At

this question is related to this question

I have a state monad. An object provides an update function as in the OOD strategy pattern.

  • The choice of having a object is that in real, production code, the class provides an array of operations, all sharing state through the monad. Inheritance helped me extend the basic functionality and further customizing the class providing the operations.
  • The choice of having a monad instead of a mutable property within the class is that the monad, through proper use of generics, is helping me abstracting and being more flexible on what variables/information must be carried along the computation as "state".

I have a simple toy example:

/////////////////////////////////////////////////////////////////////////////////////
// Definition of the state 
/////////////////////////////////////////////////////////////////////////////////////
type StateFunc<'State, 'T> = 'State -> 'T * 'State



/////////////////////////////////////////////////////////////////////////////////////
// Definition of the State monad type
/////////////////////////////////////////////////////////////////////////////////////
type StateMonadBuilder<'State>() =

    // M<'T> -> M<'T>
    member b.ReturnFrom a : StateFunc<'State, 'T> = a

    // 'T -> M<'T>
    member b.Return a : StateFunc<'State, 'T> = ( fun s ->  a, s)

    // M<'T> * ('T -> M<'U>) -> M<'U>
    member b.Bind(p : StateFunc<_, 'T>, rest : 'T -> StateFunc<_,_>) : StateFunc<'State, 'U>  = 
        (fun s ->
            let a, s' = p s
            rest a s')

    // Getter for the whole state, this type signature is because it passes along the state & returns the state
    member b.getState : StateFunc<'State, _> = (fun s -> s, s)

    // Setter for the state
    member b.putState (s:'State) : StateFunc<'State, _> = (fun _ -> (), s) 

let runState f init = f init        

/////////////////////////////////////////////////////////////////////////////////////
// STRATEGY PATTERN
/////////////////////////////////////////////////////////////////////////////////////

let state = StateMonadBuilder<int> ()

// DoubleFunctOne defines standard operations that remain always the same
type Strategy (aFunction) =
    member this.Update (x: int) = state {
        let! currState = state.getState
        let processedx = aFunction x
        do! state.putState (currState + x) }

// Create a function that customizes the strategy 
let myFunction x = 
    2 * x

// Customize the strategy with the desired function:
let strategy = Strategy (myFunction)    



/////////////////////////////////////////////////////////////////////////////////////////////////////////
// Update recursively
/////////////////////////////////////////////////////////////////////////////////////////////////////////

// ?? How to run update recursively ??
let result initialCondition = 
    initialCondition
    |> (for i = 10 to 100 do 
            yield state { do! strategy.Update i } )

My goal is to apply the initial conditions, fetch data and launch recursively (within a for or a while loop or even some functional operation) the functions provided by strategy. Working with the monad, I am not sure how to do this.

Thank you.

Computational Expression For

Inspired by @kvb answer, I have added a for method to the computational expression.

// Loops through seqnc of numbers that constitute an input to func
member b.For (seqnc:_ List, func) =
    seqnc
    |> List.map (fun item -> func item)
    |> List.reduce (fun acc item -> 
            (fun s ->
                let _, s' = acc s
                item s' ) )

I run a few tests and I have the impression that this one works. Thanks.

1

There are 1 answers

2
kvb On BEST ANSWER

Something like this?

let result initialCondition =
    let rec loop = function
    | 101 -> state { return () }
    | i -> 
        state {
            do! strategy.Update i
            do! loop (i+1)
        }
    initialCondition
    |> runState (loop 10)

Alternatively, define a For member on your builder and write it the more imperative way:

let result initialCondition =
    let f = state {
        for i in 10 to 100 do
            do! strategy.Update i
    }
    initialCondition
    |> runState f

Also, note that there is likely a bug in your definition of Strategy.Update: processedx is bound but unused.