一步一步/深入解释: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)