Пошаговое / глубокое объяснение: Сила (Со) Йонеды (предпочтительно в Скале) через сопрограммы
Некоторый фоновый код
/** 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, для любого типа B, возвращающего F[B] или (Co)Yoneda[F, Б]. Таким образом, предоставляется объединение карт бесплатно ("это что-то вроде правила обрезки для карты"). Но я вижу, что Койонеда является функтором для любого конструктора типов F, независимо от того, является ли F функтором, и что я не совсем понимаю. Сейчас я нахожусь в ситуации, когда я пытаюсь определить тип сопрограммы (я смотрю на https://www.fpcomplete.com/school/to-infinity-and-beyond/pick-of-the-week/coroutines-for-streaming/part-2-coroutines для типов, с которых можно начать)
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]
=
{ ??? }
}
}
и я думаю, что если бы я лучше понимал Coyoneda, я мог бы использовать его для упрощения использования функторов конструкторов типа S & M, плюс я вижу, что Coyoneda потенциально может сыграть роль в определении схем рекурсии, поскольку требование к функтору распространено повсеместно.
Итак, как я могу использовать coyoneda для создания функторов конструкторов типов, например, состояния сопрограммы? или что-то вроде функтора Pause?
1 ответ
Секрет Йонеды в том, что она "откладывает" необходимость Functor
экземпляр немного. Сначала это сложно, потому что мы можем определить instance Functor (Yoenda f)
без использования f
"s 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))
Но умная часть о Yoneda f a
является то, что он должен быть изоморфным f a
Однако свидетели этого изоморфизма требуют, чтобы f
это 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
Так что вместо обращения к Functor
экземпляр для f
во время определения Functor
экземпляр для Yoneda
он "откладывается" на строительство Yoneda
сам. В вычислительном отношении он также обладает прекрасным свойством превращения всех fmap
в композиции с функцией продолжения (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
Однако теперь, когда мы строим наши свидетели изоморфизма Functor
экземпляр требуется с другой стороны, при опускании CoYoenda f a
в 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
Также мы снова замечаем свойство, которое fmap
это не что иное, как композиция вдоль возможного продолжения.
Таким образом, оба из них являются способом "игнорирования" Functor
требование на некоторое время, особенно во время выполнения 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)
Этот экземпляр требует Functor
случаи как для s
а также m
типы. Можем ли мы покончить с ними с помощью 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
, тебе понадобиться Functor
экземпляры для обоих s
а также m
для того, чтобы извлечь s
а также m
типы из вашего Coroutine
, Так какой в этом смысл?
mapCoYoneda :: (forall a . f a -> g a) -> CoYoneda f a -> CoYoneda g a
mapCoYoneda phi (CoYoneda mp fb) = CoYoneda mp (phi fb)
Хорошо, если у нас есть естественная трансформация из нашего f
к g
который делает экземпляр Functor
тогда мы можем применить это в конце, чтобы извлечь наши результаты. Это структурное отображение будет применяться только один раз, а затем, после оценки fromCoYoneda
, весь стек состоит fmap
Функции педали попадут в результат.
Еще одна причина, почему вы можете играть с Yoneda
в том, что иногда можно получить Monad
случаи для Yoneda f
даже когда f
даже не Functor
, Например
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)
где мы получаем определение Monad YEndo
думая о YEndo
как CPS трансформируется Maybe
монада.
Такая работа, очевидно, бесполезна, если s
должен быть оставлен общим, но может быть полезным в случае Coroutine
конкретно. Этот пример был взят непосредственно из поста Эдварда Кметта " Свободные монады для меньших 2".