API Documentation: Bimonad

The Bimonad trait directly extends Monad and Comonad without introducing new methods. Bimonad is different from other Bi typeclasses like Bifunctor, Bifoldable or Bitraverse where the prefix describes a F[_, _]. The Bimonad is a F[_] and the Bi prefix has a different meaning here: it's both a Monad and a Comonad.
Keep in mind Bimonad has its own added laws so something that is both monadic and comonadic may not necessarily be a lawful Bimonad.

If you use Bimonad as a convenience type such that:

def f[T[_]: Monad: Comonad, S](fa: T[S]): S

is re-written to:

def f[T[_]: Bimonad, S](fa: T[S]): S

then T[_] also needs to respect an extra set of laws.

NonEmptyList as a Bimonad

NonEmptyList[_] is a lawful Bimonad so you can chain computations (like a Monad) and extract the result at the end (like a Comonad).

Here is a possible implementation:

import cats._
import cats.syntax.all._

implicit val nelBimonad: Bimonad[NonEmptyList] =
  new Bimonad[NonEmptyList] {

    // in order to have a lawful bimonad `pure` and `extract` need to respect: `nelBimonad.extract(nelBimonad.pure(a)) <-> a`
    override def pure[A](a: A): NonEmptyList[A] =

    override def extract[A](fa: NonEmptyList[A]): A =

    // use coflatMap from NonEmptyList
    override def coflatMap[A, B](fa: NonEmptyList[A])(f: NonEmptyList[A] => B): NonEmptyList[B] =

    // use flatMap from NonEmptyList
    override def flatMap[A, B](fa: NonEmptyList[A])(f: A => NonEmptyList[B]): NonEmptyList[B] =

    // the tailRecM implementation is not the subject of this material
    // as an exercise try to implement it yourself
    override def tailRecM[A, B](a: A)(fn: A => NonEmptyList[Either[A, B]]): NonEmptyList[B] =
// nelBimonad: Bimonad[NonEmptyList] = repl.MdocSession$MdocApp$$anon$1@7710adb8

Note the equivalence:

nelBimonad.pure(true).extract ===
// res0: Boolean = true

Using generic bimonad syntax we could define a function that appends and extracts a configuration:

def make[T[_]: Bimonad](config: T[String]): String = 
    .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"))

This works with one element non-empty lists:

// res1: String = "config with option A with option B with option C"

Function0[_] and Eval[_] are also lawful bimonads so the following calls are also valid:

make(() => "config")
// res2: String = "config with option A with option B with option C"

// res3: String = "config with option A with option B with option C"