The state of my program consists of three values, a
, b
, and c
, of types A
, B
, and C
. Different functions need access to different values. I want to write functions using the State
monad so that each function can only access the pieces of the state that it needs to access.
I have four functions of the following types:
f :: State (A, B, C) x
g :: y -> State (A, B) x
h :: y -> State (B, C) x
i :: y -> State (A, C) x
Here is how I call g
within f
:
f = do
-- some stuff
-- y is bound to an expression somewhere in here
-- more stuff
x <- g' y
-- even more stuff
where g' y = do
(a, b, c) <- get
let (x, (a', b')) = runState (g y) (a, b)
put (a', b', c)
return x
That g'
function is an ugly piece of boilerplate that does nothing but bridge the gap between the types (A, B, C)
and (A, B)
. It is basically a version of g
that operates on a 3-tuple state, but leaves the 3rd tuple item unchanged. I am looking for a way to write f
without that boilerplate. Maybe something like this:
f = do
-- stuff
x <- convert (0,1,2) (g y)
-- more stuff
Where convert (0,1,2)
converts a computation of type State (a, b) x
to type State (a, b, c) x
. Likewise, for all types a
, b
, c
, d
:
convert (2,0,1)
converts State (c,a) x
to State (a,b,c) x
convert (0,1)
converts State b x
to State (a,b) x
convert (0,2,1,0)
converts State (c,b) x
to State (a,b,c,d) x
My questions:
f
and g
, either F
⊆ G
or G
⊆ F
, where F
is the set of state values needed by f
and G
is the set of state values needed by g
. Am I wrong about that? (Note that my example does not satisfy this property. For example, G
= {a, b}
and H
= {b, c}
. Neither is a subset of the other.)(The functions I mentioned would look something like this.)
convert_0_1_2 :: State (a, b) x -> State (a, b, c) x
convert_0_1_2 f = do
(a, b, c) <- get
let (x, (a', b')) = runState f (a, b)
put (a', b', c)
return x
convert_0_2_1_0 :: State (c, b) x -> State (a, b, c, d) x
convert_0_2_1_0 f = do
(a, b, c, d) <- get
let (x, (b', c')) = runState f (b, c)
put (a, b', c', d)
return x
You can do it by using zoom from lens-family
or the lens
package with the tuple-lenses
package: the simplified type of zoom
is:
zoom :: Lens' s a -> State a x -> State s x
So zoom
runs a computation using a smaller state. The Lens
is used to specify the location of smaller state a
inside the larger state s
.
With these two packages, you can run g
, h
and i
as follows:
f :: State (A,B,C) x
f = do
zoom _12 g -- _12 :: Lens' (A,B,C) (A,B)
zoom _23 h -- _23 :: Lens' (A,B,C) (B,C)
zoom _13 i -- _13 :: Lens' (A,B,C) (A,C)
If you don't want to fuss around with tuples, you can use a "classy" approach with a record. There's some fancy Template Haskell to support this in the lens
package, but you can also do it by hand. The idea is to create at least one class for each piece of the state:
class HasPoints s where
points :: Lens' s Int
class ReadsPoints s where
getPoints :: Getter s Int
default getPoints :: HasPoints s => Getter s Int
getPoints = points
class SetsPoints s where
setPoints :: Setter' s Int
...
Then each function that manipulates the state will have a type signature like
fight :: (HasPoints s, ReadsHealth s) => StateT s Game Player
An action with this particular signature has full access to the points, and read-only access to the health.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With