
I'll make a random comment...
Thanks for the comments!
instance MonadReader s (State s) where ask = get local f m = fmap (fst . runState m . f) get
ahh, hadn't thought of doing that.
You want performance but this pushes more work to the compiler. If this were all functional instead of Monadic it might be simpler to start with.
My code has a lot of values in each record, maps of records, records with lists in them, etc. I actually wrote some of the code without using the state monad to start. It was a little complicated. Rewriting it with the state monad made it more modular and shorter (if you dont count all of the lifters I had to write).
withF1M = withInnerM f1 (\f r -> r {f1=f})
Main *main comment* is to separate manipulating the complex data structure from the State commands. And to make it more abstract:
get1,get3,get4 :: T1 -> Int get2 :: T1 -> T2 get1 = f1
And note the different but important design choice: You don't need to know how the data is nested to access a field.
My code doesnt only operate at the top-most layer. There are some complex state transformations that logically occur on one structure thats nested down several layers. So those operations are most naturally written with a State monad on that object. Other operations are written at a higher layer but may still need access to some lower layer state... So, if I were to follow your suggestion, I would have to make several variations of the accessors/modifiers depending on what level I were to access them from. One reason I went with the lifters was to avoid any blow-up in the number of accessors I had to write (and in hopes that later I can auto-generate them).
If you insert a T1'and'a'half data field "between" T1 and T2 then you just need to update get2/put2 to fix (get|put|mod)(3|4) and this also fixes all the code that uses any of these functions.
Yes. This is a downside. I do have some abstractions that try to hide this occasionally...
-- My choice is to use a strict modify that also returns the new value modify' :: (MonadState a m) => (a -> a) -> m a modify' f = do x <- liftM f get put $! x return x
-- Now tweakT1 can be a one-liner or longer tweakT1,tweakT1'long :: (MonadState T1 f) => Int -> Int -> f Int tweakT1 v1 v3 = liftM get4 (modify' (mod1 (+ v1) . mod3 (+ v3)))
I like it.
The compiler may or may not optimize the mod1 and mod3 into one T1 construction instead of two. If you modify parts 1 and 3 of the state in tandem a lot then
mod13 g1 g3 o@(T1 {f1=v1,f2=v2@(T2 {f3=v3})}) = o {f1=g1 v1,f2=v2 {f3=g3 v3}}
not the prettiest function in the bunch. I can see maybe using something like that if profiling said I really really needed it...
withRead $ withF2R $ withF4R $ ask And that was a very roundabout way to derive liftM get4 == withRead . withF2R . withF4R
true, but its composable and usable at all layers. I could have written "withRead $ withF4R $ ask" in a State T2 a monad or "withRead $ withXXX $ withYYY $ withF2R $ withF4R $ ask" at a much higher level... Thanks for your comments. I need to think over my code to figure out if it makes sense to hide the nestings (I suspect the answer is "sometimes"), but I think I can definitely do that in some cases. I'm starting to think that it might make sense for me to eliminate some of the nesting using cross-references (something simple like an integer ID with maps mapping IDs to objects at the top level). Then perhaps most of my state manipulations will occur at the top level (among having other benefits)... Starting to feel more DB-like. Tim Newsham http://www.thenewsh.com/~newsham/