scala haskell category-theory

Paso a paso/explicación profunda: El poder de(Co) Yoneda(preferiblemente en scala) a través de Coroutines



haskell category-theory (1)

algún código de fondo

/** 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) }

Ahora pensé que entendía yoneda y coyoneda un poco solo de los tipos, es decir, que cuantifican / resumen sobre el mapa fijo en algún tipo constructor F y algún tipo a, a cualquier tipo B que devuelve F [B] o (Co) Yoneda [F , B]. Por lo tanto, proporcionar una fusión de mapas gratis (¿es esto una especie de regla de corte para un mapa?). Pero veo que Coyoneda es un functor para cualquier constructor de tipo F, independientemente de que F sea un Functor, y eso no lo entiendo completamente. Ahora estoy en una situación en la que estoy tratando de definir un tipo de Coroutine, (estoy viendo https://www.fpcomplete.com/school/to-infinity-and-beyond/pick-of-the-week/coroutines-for-streaming/part-2-coroutines para que los tipos comiencen con

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] = { ??? } } }

y creo que si entendiera mejor a Coyoneda, podría aprovecharlo para hacer que los constructores de tipo S&M funcionen de manera muy sencilla, además veo que Coyoneda puede jugar un papel importante en la definición de esquemas de recursión, ya que el requisito del funtor es generalizado.

Entonces, ¿cómo podría usar coyoneda para hacer que los constructores de tipos funcionen como, por ejemplo, el estado coroutine? ¿O algo así como un funtor de pausa?


El secreto de Yoneda es que "difiere" un poco la necesidad de la instancia de Functor . Es complicado al principio porque podemos definir la instance Functor (Yoenda f) sin usar la instancia de f '' Functor .

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))

Pero la parte inteligente de Yoneda fa es que se supone que es isomorfo a fa , sin embargo, los testigos de este isomorfismo exigen que f sea ​​un 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

Entonces, en lugar de apelar a la instancia de Functor para f durante la definición de la instancia de Functor para Yoneda , se "difiere" a la construcción del propio Yoneda . Desde el punto de vista informático, también tiene la agradable propiedad de convertir todos los fmap s en composiciones con la función de "continuación" (a -> b) .

Ocurre lo contrario en CoYoneda . Por ejemplo, CoYoneda f sigue siendo un Functor sea ​​o no f

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

Sin embargo, ahora, cuando construimos nuestros testigos de isomorfismo, la instancia de Functor se exige en el otro lado, al bajar CoYoenda fa a fa :

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

También notamos de nuevo la propiedad de que fmap no es más que una composición a lo largo de la eventual continuación.

Por lo tanto, ambos son una forma de "ignorar" un requisito de Functor por un momento, especialmente al realizar fmap s.

Ahora hablemos de este Coroutine que creo que tiene un tipo de Haskell como

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)

Esta instancia requiere instancias de Functor para los tipos s m . ¿Podemos eliminarlos usando Yoneda o CoYoneda ? Básicamente de forma automática:

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)

pero ahora, dado que usé CoYoneda , necesitarás instancias de Functor para CoYoneda para extraer s tipos Coroutine de tu Coroutine . ¿Entonces cuál es el punto?

mapCoYoneda :: (forall a . f a -> g a) -> CoYoneda f a -> CoYoneda g a mapCoYoneda phi (CoYoneda mp fb) = CoYoneda mp (phi fb)

Bueno, si tenemos una transformación natural de nuestra f a una g lo que Functor instancia de Functor , podemos aplicarla al final para extraer nuestros resultados. Este mapeo estructural solo se aplicará una vez y luego, al evaluar desde fromCoYoneda , toda la pila de funciones compuestas de fmap de fmap alcanzará el resultado.

Otra razón por la que podrías querer jugar con Yoneda es que a veces es posible obtener instancias de Yoneda f para Yoneda f incluso cuando f no es ni siquiera un Functor . Por ejemplo

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)

donde obtenemos la definición de Monad YEndo al pensar en YEndo como una CPS transformada. Maybe monada.

Este tipo de trabajo, obviamente, no es útil si s debe dejarse en general, pero puede ser beneficioso si se ejemplifica concretamente Coroutine . Este ejemplo fue tomado directamente de la publicación Free Monads for Less 2 de Edward Kmett.