Bi-Monad

Формальное определение

Bimonad напрямую расширяет Monad и Comonad без введения новых методов. Bimonad отличается от других классов типов Bi, таких как Bifunctor, Bifoldable или Bitraverse, где префикс описывает F[_, _]. Bimonad — это F[_], а префикс Bi здесь имеет другое значение: это и монада, и комонада. Имейте в виду, что бимонада имеет свои собственные дополнительные законы, поэтому то, что является одновременно монадическим и комонадным, необязательно может быть законной бимонадой.

Определение в виде кода на Scala

trait Bimonad[F[_]] extends Monad[F], CoMonad[F]

Примеры

Непустой список

given Bimonad[NonEmptyList] with
  def unit[A](a: => A): NonEmptyList[A] = NonEmptyList(a, List.empty)

  def coUnit[A](fa: NonEmptyList[A]): A = fa.head

  extension [A](fa: NonEmptyList[A])
    def flatMap[B](f: A => NonEmptyList[B]): NonEmptyList[B] =
      fa match
        case NonEmptyList(head, Nil) => f(head)
        case NonEmptyList(head, h :: tail) =>
          f(head) ++ NonEmptyList(h, tail).flatMap(f)

    def cobind[B](f: NonEmptyList[A] => B): NonEmptyList[B] =
      NonEmptyList(f(fa), Nil)

Реализация

Реализация в Cats

import cats.*
import cats.data.*
import cats.syntax.all.*

def make[T[_]: Bimonad](config: T[String]): String = 
  config
    .flatMap(c => Bimonad[T].pure(c + " with option A"))
    .flatMap(c => Bimonad[T].pure(c + " with option B"))
    .flatMap(c => Bimonad[T].pure(c + " with option C"))
    .extract

make(NonEmptyList.one("config"))
// res0: String = "config with option A with option B with option C"

Ссылки: