一步一步/深入解释:Yoneda(最好是scala)通过协同程序的力量

一步一步/深入解释:Yoneda(最好是scala)通过协同程序的力量,scala,haskell,category-theory,Scala,Haskell,Category Theory,一些背景代码 /** 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] =

一些背景代码

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

 }
现在我想我只是从类型上理解了yoneda和coyoneda一点—— 即 它们在某种类型构造函数中固定的映射上进行量化/抽象 F和一些类型a,返回F[B]或(Co)Yoneda[F,B]的任何类型B。 因此,免费提供地图融合(?这是否类似于地图的切割规则?)。 但我看到Coyoneda是任何类型构造函数F的函子,不管F是函子, 我还没有完全理解。 现在我要定义一个协同程序类型, (我正在寻找要开始使用的类型)

我认为,如果我更好地理解Coyoneda,我可以利用它使S&M类型构造函数函子变得更容易,而且我认为Coyoneda在定义递归方案方面可能发挥作用 因为函子需求无处不在

那么,我如何使用coyoneda来生成类型构造函数函子,比如协同程序状态?
或者类似暂停函子的东西?

Yoneda的秘密在于它稍微“推迟”了对
函子
实例的需要。一开始很棘手,因为我们可以定义
实例函子(yoendaf)
,而不必使用
f
函子
实例

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))
但是关于
Yoneda f a
的聪明之处在于,它应该与
f a
同构,然而这种同构的见证人要求
f
是一个
函子

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
因此,在定义
Yoneda
函子
实例时,它不再求助于
函子
实例,而是“推迟”到
Yoneda
本身的构造。在计算上,它还具有将所有
fmap
s转换为具有“continuation”函数
(a->b)
的合成的良好特性

相反的情况发生在
CoYoneda
中。例如,
CoYoneda f
仍然是
Functor
,无论
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
然而现在,当我们构造同构时,另一边需要
函子
实例,当把
郊狼fa
降低到
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
此外,我们再次注意到,
fmap
只不过是沿最终延续的合成

因此,这两种方法都是暂时“忽略”一个
函子
需求的一种方法,尤其是在执行
fmap
s时


现在,让我们来讨论一下这个
Coroutine
,我认为它有一个类似Haskell的类型

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)
此实例这需要
s
m
类型的
Functor
实例。我们可以用
Yoneda
CoYoneda
来消除它们吗?基本上自动:

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)
但是现在,鉴于我使用了
CoYoneda
,您需要
s
m
Functor
实例,以便从
Coroutine
中提取
s
m
类型。那有什么意义呢

mapCoYoneda :: (forall a . f a -> g a) -> CoYoneda f a -> CoYoneda g a
mapCoYoneda phi (CoYoneda mp fb) = CoYoneda mp (phi fb)
好的,如果我们有一个从
f
g
的自然转换,它实例化了
函子
,那么我们可以在最后应用它来提取结果。这种结构映射只应用一次,然后,在从Coyoneda计算
后,合成的
fmap
ped函数的整个堆栈将命中结果


您可能想使用
Yoneda
的另一个原因是,有时可以为
Yoneda f
获取
Monad
实例,即使
f
甚至不是
函子。比如说

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)
在这里,我们通过将
YEndo
看作一个经过转换的
可能
单子,得到了
Monad-YEndo
的定义


如果
s
必须保持通用性,那么这种工作显然是没有用的,但是如果具体实例化
Coroutine
,这种工作可能是有益的。这个例子直接摘自Edward Kmett的帖子。

这就是协同性如何提高自由单子的性能吗?它与Yoneda、Density和Cont都是类似的类型密切相关。然而,与Yoneda中的
fmap
ing那样获得合成不同,Codensity恰好关联了所有绑定,这样它们就不必一直遍历整个自由单子。要看到这一点,请尝试使用定义
m>=k=Codensity(\c->runCodensity m(\a->runCodensity(ka)c))扩展
(m>=n)>=l
其中
Codensity
当然是
newtype Codensity fa=Codensity{runCodensity::forall r.(a->fr)->fr}
或某种
类型Codensity fa=forall r。ContT r f a
Codensity对于工作来说有点“太大”,例如,读卡器的Codensity同构于状态,而不是读卡器。Edward Kmett的免费Monad for Less 2也包含了这个结果。@Mzk:的确,Codensity和Yoneda是(不同的)正确的扩展。例如,
键入Yoneda=Ran-Identity
()。和
Codensity f=Ran f f
()。很容易看出类型的平等性,但看到有能力的人(Edward Kmett)确认这不是一个意外,这让人感到安慰。但我也没有能力谈论菅直人的扩展:你最好研究科米特的帖子和/或提出新问题。
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)