问题
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
回答1:
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)
回答2:
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.
来源:https://stackoverflow.com/questions/33935835/mix-and-match-stateful-computations-within-the-state-monad