Comonad
doesn't give you any way to write back to the comonad's focus, so you can't write a general Lens
for extract
. But it is very easy to turn any old function into a Getter
:
extracted :: Comonad w => Getter (w a) a
extracted = to extract
Of course many comonads do allow you to write into the focus - Store
, as you mentioned, but also (including but not limited to) Env
, Traced
and Identity
.
-- I suspect some of these laws are redundant:
-- write id = id
-- write f . write g = write (f . g)
-- extract . write f = f . extract
-- duplicate . write f = write (write f) . duplicate
-- write (const (extract w)) w = w
class Comonad w => ComonadWritable w where
write :: (a -> a) -> w a -> w a
instance ComonadWritable Identity where
write f (Identity x) = Identity (f x)
-- law-abiding "up to Eq"
instance (Eq s, ComonadWritable w) => ComonadWritable (StoreT s w) where
write f (StoreT w s) = StoreT (write g w) s
where
g k s'
| s' == s = f (k s')
| otherwise = k s'
-- law-abiding "up to Eq"
instance (Eq m, Monoid m, ComonadWritable w) => ComonadWritable (TracedT m w) where
write f (TracedT w) = TracedT (write g w)
where
g k m
| m == mempty = f (k m)
| otherwise = k m
instance ComonadWritable w => ComonadWritable (EnvT e w) where
write f (EnvT e w) = EnvT e (write f w)
Given ComonadWritable
it's easy to construct a Lens
for a comonad's focus.
focus :: ComonadWritable w => Lens' (w a) a
focus = lens extract (\w x -> write (const x) w)
One note on efficiency: StoreT
and TracedT
's write
implementations build a chain of functions with equality checks on the way down, so extract
is O(n) in the number of write
calls. Since you mentioned you're using a Representable
comonad w
, you could implement some clever strategy of batching up edits and reifying them into an actual w
every so often. Or you could store edits in a Map
(strengthening the Eq
constraint to Ord
) and delegate to the underlying w
when it turns out an element hasn't been edited. I'll leave that part to you.