CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-typelevel--cats-free-2-11

Free monads and related constructs for functional programming in Scala, providing Free and FreeT monads, Free applicatives, Cofree comonads, and Yoneda lemmas for DSL embedding and performance optimization.

Overview
Eval results
Files

cofree.mddocs/

Cofree Comonads

Cofree comonads are the dual of Free monads, representing infinite data structures with context-dependent computation. They're useful for modeling streams, environments, and other structures where you need to extract values from context.

Core API

final case class Cofree[S[_], A](head: A, tail: Eval[S[Cofree[S, A]]])

// Construction
def unfold[F[_], A](a: A)(f: A => F[A])(implicit F: Functor[F]): Cofree[F, A]
def ana[F[_], A, B](a: A)(coalg: A => F[A], f: A => B)(implicit F: Functor[F]): Cofree[F, B]
def anaEval[F[_], A, B](a: A)(coalg: A => Eval[F[A]], f: A => B)(implicit F: Functor[F]): Cofree[F, B]

// Access
def head: A
def tail: Eval[S[Cofree[S, A]]]
def tailForced: S[Cofree[S, A]]
def extract: A  // Alias for head

// Comonadic Operations
def coflatMap[B](f: Cofree[S, A] => B)(implicit S: Functor[S]): Cofree[S, B]
def coflatten(implicit S: Functor[S]): Cofree[S, Cofree[S, A]]

// Transformations
def map[B](f: A => B)(implicit S: Functor[S]): Cofree[S, B]
def transform[B](f: A => B, g: Cofree[S, A] => Cofree[S, B])(implicit S: Functor[S]): Cofree[S, B]
def mapBranchingRoot(nat: S ~> S)(implicit S: Functor[S]): Cofree[S, A]
def mapBranchingS[T[_]](nat: S ~> T)(implicit S: Functor[S]): Cofree[T, A]
def mapBranchingT[T[_]](nat: S ~> T)(implicit T: Functor[T]): Cofree[T, A]

// Evaluation
def forceTail: Cofree[S, A]
def forceAll(implicit S: Functor[S]): Cofree[S, A]

Factory Methods

// Catamorphisms
def cata[F[_], A, B](cof: Cofree[F, A])(folder: (A, F[B]) => Eval[B])(implicit F: Traverse[F]): Eval[B]
def cataM[F[_], M[_], A, B](cof: Cofree[F, A])(folder: (A, F[B]) => M[B])(inclusion: Eval ~> M)(implicit F: Traverse[F], M: Monad[M]): M[B]

Type Class Instances

implicit def catsFreeComonadForCofree[S[_]: Functor]: Comonad[Cofree[S, ?]]
implicit def catsFreeTraverseForCofree[S[_]: Traverse]: Traverse[Cofree[S, ?]]

Basic Usage

Infinite Streams

import cats.implicits._

// Create an infinite stream of integers
val naturals: Cofree[Option, Int] = Cofree.unfold(1)(n => Some(n + 1))

// Access elements
val first = naturals.head        // 1
val second = naturals.tail.value.map(_.head).getOrElse(0)  // 2

// Map over the stream
val doubled: Cofree[Option, Int] = naturals.map(_ * 2)
val firstDoubled = doubled.head  // 2

Rose Trees (Multi-way Trees)

// Rose tree structure
type RoseTree[A] = Cofree[List, A]

// Create a tree
val tree: RoseTree[String] = Cofree(
  "root",
  Eval.now(List(
    Cofree("child1", Eval.now(List(
      Cofree("grandchild1", Eval.now(Nil)),
      Cofree("grandchild2", Eval.now(Nil))
    ))),
    Cofree("child2", Eval.now(Nil))
  ))
)

// Extract the root
val root = tree.extract  // "root"

// Map over all nodes
val uppercased: RoseTree[String] = tree.map(_.toUpperCase)

Environment-Based Computation

// Configuration environment
case class Config(baseUrl: String, timeout: Int, retries: Int)

// Environment stack - each level can have different config
type Environment[A] = Cofree[List, (Config, A)]

def withConfig[A](config: Config, value: A, children: List[Environment[A]]): Environment[A] =
  Cofree((config, value), Eval.now(children))

// Create nested environment
val env: Environment[String] = withConfig(
  Config("http://api.example.com", 5000, 3),
  "api",
  List(
    withConfig(
      Config("http://api.example.com", 1000, 1), 
      "fast-endpoint", 
      Nil
    ),
    withConfig(
      Config("http://api.example.com", 10000, 5),
      "slow-endpoint",
      Nil
    )
  )
)

// Extract configuration at any level
def getConfig[A](env: Environment[A]): Config = env.extract._1
def getValue[A](env: Environment[A]): A = env.extract._2

val rootConfig = getConfig(env)  // Config("http://api.example.com", 5000, 3)
val rootValue = getValue(env)    // "api"

Advanced Usage

Cofree with Custom Functors

// Binary tree functor
sealed trait BinaryF[A]
case class Node[A](left: A, right: A) extends BinaryF[A]
case class Leaf[A]() extends BinaryF[A]

implicit val binaryFunctor: Functor[BinaryF] = new Functor[BinaryF] {
  def map[A, B](fa: BinaryF[A])(f: A => B): BinaryF[B] = fa match {
    case Node(left, right) => Node(f(left), f(right))
    case Leaf()            => Leaf()
  }
}

type BinaryTree[A] = Cofree[BinaryF, A]

// Create a binary tree
def node[A](value: A, left: BinaryTree[A], right: BinaryTree[A]): BinaryTree[A] =
  Cofree(value, Eval.now(Node(left, right)))

def leaf[A](value: A): BinaryTree[A] =
  Cofree(value, Eval.now(Leaf()))

val binaryTree: BinaryTree[Int] = node(
  1,
  node(2, leaf(4), leaf(5)),
  node(3, leaf(6), leaf(7))
)

Cofree Catamorphisms

// Sum all values in a binary tree
def sumTree(tree: BinaryTree[Int]): Int = {
  val folder: (Int, BinaryF[Int]) => Eval[Int] = {
    case (value, Node(left, right)) => Eval.now(value + left + right)
    case (value, Leaf())            => Eval.now(value)
  }
  
  Cofree.cata(tree)(folder).value
}

val total = sumTree(binaryTree)  // 28

Zipper-like Navigation

// Navigate through a Cofree structure
def navigate[S[_]: Functor, A](cofree: Cofree[S, A]): NavigationContext[S, A] = {
  NavigationContext(cofree, Nil)
}

case class NavigationContext[S[_], A](
  current: Cofree[S, A],
  breadcrumbs: List[S[Cofree[S, A]]]
) {
  def extract: A = current.extract
  
  def down(selector: S[Cofree[S, A]] => Option[Cofree[S, A]])(implicit S: Functor[S]): Option[NavigationContext[S, A]] = {
    selector(current.tailForced).map { next =>
      NavigationContext(next, current.tailForced :: breadcrumbs)
    }
  }
  
  def up: Option[NavigationContext[S, A]] = breadcrumbs.headOption.map { parent =>
    NavigationContext(Cofree(current.extract, Eval.now(parent)), breadcrumbs.tail)
  }
}

// Example usage with List functor (non-deterministic choice)
val listCofree: Cofree[List, String] = Cofree("root", Eval.now(List(
  Cofree("child1", Eval.now(Nil)),
  Cofree("child2", Eval.now(Nil))
)))

val nav = navigate(listCofree)
val maybeChild = nav.down(_.headOption)  // Navigate to first child

Streaming with Cofree

import cats.effect.IO

// Create an effectful stream
def effectfulStream[A](initial: A)(next: A => IO[Option[A]]): IO[Cofree[Option, A]] = {
  def go(current: A): IO[Cofree[Option, A]] = {
    next(current).flatMap {
      case Some(nextVal) => go(nextVal).map(tail => Cofree(current, Eval.now(Some(tail))))
      case None          => IO.pure(Cofree(current, Eval.now(None)))
    }
  }
  go(initial)
}

// Fibonacci stream
val fibStream: IO[Cofree[Option, (BigInt, BigInt)]] = effectfulStream((BigInt(0), BigInt(1))) {
  case (a, b) => IO.pure(Some((b, a + b)))
}

// Take first n elements
def take[A](n: Int, cofree: Cofree[Option, A]): List[A] = {
  if (n <= 0) Nil
  else {
    cofree.extract :: (cofree.tailForced match {
      case Some(tail) => take(n - 1, tail)
      case None       => Nil
    })
  }
}

Performance Considerations

  • Cofree uses Eval for lazy evaluation of the tail structure
  • Use forceTail only when you need immediate evaluation
  • forceAll forces the entire structure - use carefully with infinite structures
  • Consider using catamorphisms for efficient tree traversals
  • The comonadic operations preserve the infinite nature of the structure

Install with Tessl CLI

npx tessl i tessl/maven-org-typelevel--cats-free-2-11

docs

cofree.md

free-applicative.md

free-monads.md

free-transformer.md

index.md

interpreters.md

trampoline.md

yoneda-coyoneda.md

tile.json