Мы можем описать монаду как вычислительный контекст, и реализация монады в точности сохраняет значение этого контекста. Например, Option — контекст означает, что значение может существовать. Учитывая тип данных Option, единственная реализация, которая имеет смысл, это pure = some, flatMap f = {none => none; some x => f x }
Как я понимаю монады, следуя сигнатурам типов - есть только одна разумная реализация для любой монады. Другими словами, если вы хотите добавить какой-то осмысленный контекст к значению/вычислению, есть только один способ сделать это для любой конкретной монады.
С другой стороны, когда дело доходит до комонады, это внезапно начинает казаться совершенно странным, как будто есть много-много способов реализовать комонаду для данного типа, и вы можете даже дать определенное значение для каждой реализации.
Рассмотрим, NEL, с copure = head
. cojoin
реализуется через tails
, что идеально соответствует типам. Если мы реализуем cojoin
с помощью permutations
или как fa map (_ => fa) map f
, это не будет удовлетворять законам комонады.
Но реализация обводки допустима:
override def cobind[A, B](fa: NonEmptyList[A])(f: (NonEmptyList[A]) => B): NonEmptyList[B] = {
val n: NonEmptyList[NonEmptyList[A]] = fa.map(_ => fa).zipWithIndex.map { case (li , i ) =>
val(h: List[A], t: List[A]) = li.list.splitAt(i)
val ll: List[A] = t ++ h
NonEmptyList.nel(ll.head, ll.tail)
}
n map f
}
Причина такой неоднозначности для Команды, даже при наличии ограничивающих нас законов, как мне видится, в том, что если в Монаде мы ограничиваем себя в каком-то контексте (мы как бы не можем «создать» новую информацию), то в Комонаде мы расширяя этот контекст дальше (есть довольно много способов сделать список списков из списка), что дает нам гораздо больше возможностей для этого.В моей голове метафора: для Монады мы стоим на дороге и хотим достичь некоторого точка назначения A = следовательно, есть только осмысленный кратчайший путь для выбора. В команде мы находимся в точке A и хотим куда-то отправиться из нее, так что есть гораздо больше способов сделать это.
Итак, мой вопрос: Я на самом деле прав? Можем ли мы реализовать команду по-разному, каждый раз делая новую осмысленную абстракцию? Или только реализация хвостов является разумной из-за абстракции, которую должна привнести комонада.