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