some background code
/** FunctorStr: ∑ F[-]. (∏ A B. (A -> B) -> F[A] -> F[B]) */ trait FunctorStr[F[_]] { self => def map[A, B](f: A => B): F[A] => F[B] } trait Yoneda[F[_], A] { yo => def apply[B](f: A => B): F[B] def run: F[A] = yo(x => x) def map[B](f: A => B): Yoneda[F, B] = new Yoneda[F, B] { def apply[X](g: B => X) = yo(f andThen g) } } object Yoneda { implicit def yonedafunctor[F[_]]: FunctorStr[({ type l[x] = Yoneda[F, x] })#l] = new FunctorStr[({ type l[x] = Yoneda[F, x] })#l] { def map[A, B](f: A => B): Yoneda[F, A] => Yoneda[F, B] = _ map f } def apply[F[_]: FunctorStr, X](x: F[X]): Yoneda[F, X] = new Yoneda[F, X] { def apply[Y](f: X => Y) = Functor[F].map(f) apply x } } trait Coyoneda[F[_], A] { co => type I def fi: F[I] def k: I => A final def map[B](f: A => B): Coyoneda.Aux[F, B, I] = Coyoneda(fi)(f compose k) } object Coyoneda { type Aux[F[_], A, B] = Coyoneda[F, A] { type I = B } def apply[F[_], B, A](x: F[B])(f: B => A): Aux[F, A, B] = new Coyoneda[F, A] { type I = B val fi = x val k = f } implicit def coyonedaFunctor[F[_]]: FunctorStr[({ type l[x] = Coyoneda[F, x] })#l] = new CoyonedaFunctor[F] {} trait CoyonedaFunctor[F[_]] extends FunctorStr[({type l[x] = Coyoneda[F, x]})#l] { override def map[A, B](f: A => B): Coyoneda[F, A] => Coyoneda[F, B] = x => apply(x.fi)(f compose x.k) } def liftCoyoneda[T[_], A](x: T[A]): Coyoneda[T, A] = apply(x)(a => a) }
Now I thought I understood yoneda and coyoneda a bit just from the types – i.e. that they quantify / abstract over map fixed in some type constructor F and some type a, to any type B returning F[B] or (Co)Yoneda[F, B]. Thus providing map fusion for free (? is this kind of like a cut rule for map ?). But I see that Coyoneda is a functor for any type constructor F regardless of F being a Functor, and that I don't fully grasp. Now I'm in a situation where I'm trying to define a Coroutine type, (I'm looking at https://www.fpcomplete.com/school/to-infinity-and-beyond/pick-of-the-week/coroutines-for-streaming/part-2-coroutines for the types to get started with)
case class Coroutine[S[_], M[_], R](resume: M[CoroutineState[S, M, R]]) sealed trait CoroutineState[S[_], M[_], R] object CoroutineState { case class Run[S[_], M[_], R](x: S[Coroutine[S, M, R]]) extends CoroutineState[S, M, R] case class Done[R](x: R) extends CoroutineState[Nothing, Nothing, R] class CoroutineStateFunctor[S[_], M[_]](F: FunctorStr[S]) extends FunctorStr[({ type l[x] = CoroutineState[S, M, x]})#l] { override def map[A, B](f : A => B) : CoroutineState[S, M, A] => CoroutineState[S, M, B] = { ??? } } }
and I think that if I understood Coyoneda better I could leverage it to make S & M type constructors functors way easy, plus I see Coyoneda potentially playing a role in defining recursion schemes as the functor requirement is pervasive.
So how could I use coyoneda to make type constructors functors like for example coroutine state? or something like a Pause functor ?
The secret of Yoneda is that it "defers" the need for the Functor
instance a bit. It's tricky at first because we can define instance Functor (Yoenda f)
without using f
's Functor
instance.
newtype Yoneda f a = Yoneda { runYoneda :: forall b . (a -> b) -> f b } instance Functor (Yoneda f) where fmap f y = Yoneda (\ab -> runYoneda y (ab . f))
But the clever part about Yoneda f a
is that it's supposed to be isomorphic to f a
, however the witnesses to this isomorphism demand that f
is a Functor
:
toYoneda :: Functor f => f a -> Yoneda f a toYoneda fa = Yoneda (\f -> fmap f fa) fromYoneda :: Yoneda f a -> f a fromYoneda y = runYoneda y id
So instead of appealing to the Functor
instance for f
during definition of the Functor
instance for Yoneda
, it gets "defered" to the construction of the Yoneda
itself. Computationally, it also has the nice property of turning all fmap
s into compositions with the "continuation" function (a -> b)
.
The opposite occurs in CoYoneda
. For instance, CoYoneda f
is still a Functor
whether or not f
is
data CoYoneda f a = forall b . CoYoneda (b -> a) (f b) instance Functor (CoYoneda f) where fmap f (CoYoneda mp fb) = CoYoneda (f . mp) fb
however now when we construct our isomorphism witnesses the Functor
instance is demanded on the other side, when lowering CoYoenda f a
to f a
:
toCoYoneda :: f a -> CoYoneda f a toCoYoneda fa = CoYoneda id fa fromCoYoneda :: Functor f => CoYoneda f a -> f a fromCoYoneda (CoYoneda mp fb) = fmap mp fb
Also we again notice the property that fmap
is nothing more than composition along the eventual continuation.
So both of these are a way of "ignoring" a Functor
requirement for a little while, especially while performing fmap
s.
Now let's talk about this Coroutine
which I think has a Haskell type like
data Coroutine s m r = Coroutine { resume :: m (St s m r) } data St s m r = Run (s (Coroutine s m r)) | Done r instance (Functor s, Functor m) => Functor (Coroutine s m) where fmap f = Coroutine . fmap (fmap f) . resume instance (Functor s, Functor m) => Functor (St s m) where fmap f (Done r) = Done (f r) fmap f (Run s ) = Run (fmap (fmap f) s)
This instance this requires Functor
instances for both the s
and m
types. Can we do away with them by using Yoneda
or CoYoneda
? Basically automatically:
data Coroutine s m r = Coroutine { resume :: CoYoneda m (St s m r) } data St s m r = Run (CoYoneda s (Coroutine s m r)) | Done r instance Functor (Coroutine s m) where fmap f = Coroutine . fmap (fmap f) . resume instance Functor (St s m) where fmap f (Done r) = Done (f r) fmap f (Run s ) = Run (fmap (fmap f) s)
but now, given that I used CoYoneda
, you'll need Functor
instances for both s
and m
in order to extract s
and m
types out of your Coroutine
. So what's the point?
mapCoYoneda :: (forall a . f a -> g a) -> CoYoneda f a -> CoYoneda g a mapCoYoneda phi (CoYoneda mp fb) = CoYoneda mp (phi fb)
Well, if we have a natural transformation from our f
to a g
which does instantiate Functor
then we can apply that at the end in order to extract our results. This structural mapping will only apply once and then, upon evaluating fromCoYoneda
, the entire stack of composed fmap
ped functions will hit the result.
Another reason why you might want to play with Yoneda
is that it's sometimes possible to get Monad
instances for Yoneda f
even when f
isn't even a Functor
. For instance
newtype Endo a = Endo { appEndo :: a -> a } -- YEndo ~ Yoneda Endo data YEndo a = YEndo { yEndo :: (a -> b) -> (b -> b) } instance Functor YEndo where fmap f y = YEndo (\ab -> yEndo y (ab . f)) instance Monad YEndo where return a = YEndo (\ab _ -> ab a) y >>= f = YEndo (\ab b -> yEndo y (\a -> yEndo (f a) ab b) b)
where we get the definition of Monad YEndo
by thinking of YEndo
as a CPS transformed Maybe
monad.
This kind of work obviously isn't useful if s
must be left general, but can be beneficial if instantiating Coroutine
concretely. This example was taken directly from Edward Kmett's post Free Monads for Less 2.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With