Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add example for State monad #416

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions FSharpx.Extras.sln
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{A6A6AF7D-D
docs\index.fsx = docs\index.fsx
docs\Santa.fsx = docs\Santa.fsx
docs\StmSample.fsx = docs\StmSample.fsx
docs\StateSample.fsx = docs\StateSample.fsx
docs\StructuredFormatSample.fsx = docs\StructuredFormatSample.fsx
docs\UndoSample.fsx = docs\UndoSample.fsx
docs\WebProxy.fsx = docs\WebProxy.fsx
Expand Down
100 changes: 100 additions & 0 deletions docs/StateSample.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#r @"../bin/FSharpx.Extras.dll"
#r @"../bin/FSharpx.Collections.dll"

open System
open System.Threading
open FSharpx

type MyStateRecord =
{
X:int
Y:string
}
type MyResultType =
{
R: double
}

let ``nested step without result`` : State.State<unit,MyStateRecord> =
State.state {
printfn "Starting ``nested step without result``"

let! s = State.getState
do! State.putState {s with X = s.X+1}
printfn "Ending ``nested step without result``"
}

let ``nested step with same state type, but different result type`` : State.State<char,MyStateRecord> =
State.state {
printfn "Starting ``nested step with same state type, but different result type``"

printfn "Getting the state sn1 inside nested step "
let! sn1 = State.getState
printfn "sn1: %A" sn1

printfn "Writing state inside nested step"
do! State.putState {sn1 with X=0; Y="nested Step"}
printfn "Getting the state sn2 inside nested step "
let! sn2 = State.getState

printfn "Notice that put does not mutate sn1"
printfn "sn1: %A" sn1
printfn "sn2: %A" sn2

printfn "Ending ``nested step with same state type, but different result type``"
return (char sn1.X)
}

let ``computation using state monad`` : State.State<MyResultType, MyStateRecord> =
State.state {
printfn "Starting ``computation using state monad``"
printfn "Getting state value s1"
let! s1 = State.getState
printfn "s1: %A" s1

printfn "Writing state"
do! State.putState {s1 with Y = "first put"}
printfn "Getting state value s2"
let! s2 = State.getState
printfn "s2: %A" s2

printfn "Start nested step resulting in c1"
let! c1 = ``nested step with same state type, but different result type``
printfn "End nested step resulting in c1"
printfn "Getting state value s3"
let! s3 = State.getState
printfn "c1: %A, s3: %A" c1 s3

printfn "Start nested step without result (increases X by one)"
do! ``nested step without result``
printfn "End nested step resulting"
printfn "Getting state value s4"
let! s4 = State.getState
printfn "s4: %A" s4

printfn "double c1: %A double s4.X: %A" (double c1) (double s4.X)
printfn "Ending ``computation using state monad``"
return {R= 123.0 + double c1 + double s4.X}
}

let run () =
let startingState = {X = 42; Y="start"}

printfn "You can get both result and state by providing the State monad with start state"
printfn "(Notice that computation won't start until you provide `startState`)"
let (result, endState) = ``computation using state monad`` startingState
printfn "result: %A, endState: %A" result endState
printfn ""
printfn ""
printfn ""
printfn "Or result only with `eval`"
printfn "(Notice recomputation)"
let resultOnly = State.eval ``computation using state monad`` startingState
printfn "resultOnly: %A" resultOnly
printfn ""
printfn ""
printfn ""
printfn "Or endState only with `exec`"
let onlyEndState = State.exec ``computation using state monad`` startingState
printfn "onlyEndState: %A" onlyEndState
()
14 changes: 10 additions & 4 deletions src/FSharpx.Extras/ComputationExpressions/State.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@ module State =

type State<'T, 'State> = 'State -> 'T * 'State

/// Returns state value from the monad.
let getState = fun s -> (s,s)
/// Replaces state inside the monad.
let putState s = fun _ -> ((),s)
/// Evaluates a state computation with the given initial state and returns the final value, discarding the final state.
let eval m s = m s |> fst
/// Evaluates a state computation with the given initial state and returns the final state, discarding the final value.
let exec m s = m s |> snd
/// Instance of state monad without value
let empty = fun s -> ((), s)
/// Sequentially compose two actions, passing any value produced by the first as an argument to the second.
let bind k m = fun s -> let (a, s') = m s in (k a) s'

/// The state monad.
Expand Down Expand Up @@ -68,12 +74,12 @@ module State =
let inline (>=>) f g = fun x -> f x >>= g
/// Right-to-left Kleisli composition
let inline (<=<) x = flip (>=>) x

let foldM f s =
/// Fold encapsulated in the State monad
let foldM f s =
Seq.fold (fun acc t -> acc >>= (flip f) t) (returnM s)

/// Evaluates each monadic action in the list from left to right, and collects the results.
let inline sequence s =
let inline cons a b = lift2 List.cons a b
List.foldBack cons s (returnM [])

/// Maps each element of a list to a monadic action, evaluates these actions from left to right, and collects the results.
let inline mapM f x = sequence (List.map f x)