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)
convertsState (c,a) x
toState (a,b,c) x
convert (0,1)
convertsState b x
toState (a,b) x
convert (0,2,1,0)
convertsState (c,b) x
toState (a,b,c,d) x
My questions:
- Is there a better solution than putting state values in tuples? I thought about using a monad transformer stack. However, I think that only works if, for any two functions
f
andg
, eitherF
⊆G
orG
⊆F
, whereF
is the set of state values needed byf
andG
is the set of state values needed byg
. Am I wrong about that? (Note that my example does not satisfy this property. For example,G
={a, b}
andH
={b, c}
. Neither is a subset of the other.) - If there is no better way than tuples, then is there a good way to avoid the boilerplate I mentioned? I am even willing to write a file with a bunch of boilerplate functions (see below) as long as the file can be automatically generated once and then forgotten about. Is there a better way? (I have read about lenses, but their complexity, ugly syntax, enormous set of unnecessary features, and seeming reliance on Template Haskell are off-putting. Is that a misconception of mine? Can lenses solve my problem in a way that avoids those problems?)
(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
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:Then each function that manipulates the state will have a type signature like
An action with this particular signature has full access to the points, and read-only access to the health.
You can do it by using zoom from
lens-family
or thelens
package with thetuple-lenses
package: the simplified type ofzoom
is:So
zoom
runs a computation using a smaller state. TheLens
is used to specify the location of smaller statea
inside the larger states
.With these two packages, you can run
g
,h
andi
as follows: