In the reddit thread about Don't Fear The Profunctor Optics there is discussion about Monoidal
.
Discussion is about what's Monoidal
?
class Profunctor p => Monoidal p where
par :: p a b -> p c d -> p (a, c) (b, d)
empty :: p () ()
Monoidal
is used to implement traversals. As a concrete example we can pick lists:
listTraversing :: (Choice p, Monoidal p) ->
=> p a b -> p [a] [b]
listTraversing k = go where
go = dimap out inn (right (par k go))
out :: [a] -> Either () (a, [a])
out [] = Left ()
out (x:xs) = Right (x, xs)
inn :: Either () (a, [a]) -> [a]
inn (Left ()) = []
inn (Right (x, xs)) = x : xs
When [a]
comes in, we pattern match it with out
to convert it into Either () (a, [a])
(and use inn
to convert back). We let a unit ()
pass-through by using right
(note: we need Choice
). And using Monoidal
's par
we can combine k :: p a b
and recursive application of listTraversing
to get p (a, [a]) (b, [b])
.
Another example: both
for a homogenic pair. Here we don't need Choice
!
both :: Monoidal p => p a b -> p (a, a) (b, b)
both k = par k k
My point: Monoidal
(by itself) is only powerful enough to express traversals over Representable
containers, and only where Rep f
is finite! The idea is that, as Rep f
is finite we have a bijection between f a
and Vec n a
. for some static n :: Nat
.
data Nat where
Z :: Nat
S :: Nat -> Nat
data SNat (n :: Nat) where
SZ :: SNat 'Z
SS :: INat n => SNat ('S n)
class INat (n :: Nat) where inat :: SNat n
instance INat 'Z where inat = SZ
instance INat n => INat ('S n) where inat = SS
data Vec n a where
VNil :: Vec 'Z a
VCons :: a -> Vec n a -> Vec ('S n) a
vecTraversing :: forall n p a b. (INat n, Monoidal p)
=> p a b -> p (Vec n a) (Vec n b)
vecTraversing k = case inat :: SNat n of
SZ -> dimap (const ()) (const VNil) empty
SS -> dimap out inn (par k (vecTraversing k))
where
out :: forall m. INat m => Vec ('S m) a -> (a, Vec m a)
out (VCons x xs) = (x, xs)
inn :: forall m. (b, Vec m b) -> Vec ('S m) b
inn (x, xs) = VCons x xs
At this point point it starts to be clear that Traversing
class (Choice p, Strong p) => Traversing p where
traverse' :: Traversable f => p a b -> p (f a) (f b)
wander :: (forall f. Applicative f => (a -> f b) -> s -> f t)
-> p a b -> p s t
is more powerful than Monoidal
alone.
It can do Choice
(it can also do Strong
, which Monoidal
cannot without arr :: p a a
!). Quite importantly Traversing
can handle infinite structures. I don't have a good argument for or against whether Traversal
s should consider only finite structures, yet traversals over infinite structures are useful in practice.
On the other hand, as davemenendez
mentions, we cannot define Monoidal
using Traversing
! Informally we could say that the relative power is something like:
Monoidal < Traversing < Monoidal + Choice + Strong
It's a very good question, if there is practical difference.
On the other hand, and to conclude: there is a Indexed Containers paper discussing containers. And I think that something like that is needed so we can have three classes of optics:
Then we'll have solid theoretical foundation for optics (IMHO), though probably not expressable in Haskell.