COMONADS
so in the 3d haskell rhombic dodecahedron game / game engine, there's a lot of times when we want to update a given cell by checking its neighbors. currently the way this works is: if i want to do this, i write a function
a few days ago i read this blog post about comonads and cellular automatia and while that's structured more around zippers as comonads the same basic insight made me realize i can absolutely generalize the "lookup adjacent cells" part into something very much like the comonadic
(btw that blog post uses
since i'd rather not rework this type into zipper format just so i can technically instance it to comonad, with all the weird zipper-based overhead that would probably entail, i decided to just fake it and write my own
this at the very least makes all this code feel less hacky, since the neighbor lookup is all centralized in one function rather than scattered all over the place. it's probably just as slow though. it probably would not hurt to compile all of the vmap/map generation code as strict, just to see what speed improvements i get. but that's for, uh, not right now.
but what's nice is that now i have kind of an intuition for what a comonad is: it's something where
MONAD TUTORIALS AND COMONAD TUTORIALS. maybe next i'll find a use for contrafunctors.
VMap a -> VMap b (for some concrete type a and b) and then i write some code that maps across the vmap (that being the data store i use to store all the rhombic data; it's basically a vector with some extra indexing functions so i can relabel all the coordinates in O(1) time) to look up all the adjacent cells and do whatever i want with the neighbor data that turns up. this is a huge bottleneck in the code, for reasons still unknown to me but probably having to do with laziness, and it's also super awkward to write.a few days ago i read this blog post about comonads and cellular automatia and while that's structured more around zippers as comonads the same basic insight made me realize i can absolutely generalize the "lookup adjacent cells" part into something very much like the comonadic
=>> / extend. if i have a vmap, then an actual extend function would be extend :: (VMap a -> b) -> VMap a -> VMap b, which isn't useful -- there's a reason that post talks about zippers, and that's because a zipper has a coherent and justifiable value to give to extract :: Comonad m => m a -> a, and that's the currectly-selected value. if you don't have a zipper, you gotta pick something arbitrary, and there's no coherent way to write cojoin / duplicate.(btw that blog post uses
=>>, coreturn, and cojoin as the comonad class names; the actual Control.Comonad library uses extend, extract, and duplicate.)since i'd rather not rework this type into zipper format just so i can technically instance it to comonad, with all the weird zipper-based overhead that would probably entail, i decided to just fake it and write my own
extend function: extend :: ([(RD Integer, a)] -> b) -> VMap a -> VMap b (or _extend :: Coordinate i f v => ([(i, a)] -> b) -> VMapI i a -> VMapI i b for the still-unfinished arbitrary-index version)this at the very least makes all this code feel less hacky, since the neighbor lookup is all centralized in one function rather than scattered all over the place. it's probably just as slow though. it probably would not hurt to compile all of the vmap/map generation code as strict, just to see what speed improvements i get. but that's for, uh, not right now.
but what's nice is that now i have kind of an intuition for what a comonad is: it's something where
extend :: Comonad m => (m a -> b) -> m a -> m b makes sense, a type that provides a comonad context that can be reduced down to a single value, only to have that reduced value slotted pack into place -- and thus a type where there's some kind of positional or locational data associated with the type itself (and potentially with each value too), among other kinds of "context".MONAD TUTORIALS AND COMONAD TUTORIALS. maybe next i'll find a use for contrafunctors.