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/
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe