As I understand it, each van Laarhoven optic type can be defined by a constraint on a type constructor:
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
type Traversal s t a b = forall f. Applicative f => (a -> f b) -> s -> f t
-- etc.
If we choose Monad as the constraint, does it form some kind of "optic" in a meaningful way?
type Something s t a b = forall f. Monad f => (a -> f b) -> s -> f t
My intuition is that the Monad constraint might be too restrictive to get any value out of a structure like this: since the Const functor is not a Monad, we can't do the trick of specializing f to Const in order to derive a view-like function. Still, we can do some things with this Something type; it's just not clear to me if we can do anything particularly useful with it.
The reason I'm curious is because the type of a van Laarhoven optic is suspiciously similar to the type of a function that modifies a "mutable reference" type like IORef. For example, we can easily implement
modifyIORefM :: MonadIO m => IORef a -> (a -> m a) -> () -> m ()
which, when partially-applied to an IORef, has the shape
type SomethingIO s t a b = forall f. MonadIO f => (a -> f b) -> s -> f t
where a = b and s = t = (). I'm not sure whether this is a meaningful or meaningless coincidence.
Practically speaking, such an optic is a slightly inconvenient
Traversal.That's because, practically speaking, we use a
Traversal:for two things. Getting a list of
as from ans, which we can do with theConstfunctor:and replacing the
as withbs to turn thesinto at. One method is to use theStatefunctor, and ignoring issues with matching the counts ofas andbs, we have:If we instead have an optic using a
Monadconstraint:we can still perform these two operations. Since
Stateis a monad, thesetListOfoperation can use the same implementation:For
toListOf, there's noMonadinstance forConst [a], but we can use aWritermonad to extract theavalues, as long as we have a dummybvalue to make the type checker happy:or, since Haskell has bottom:
Self-contained code: