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

free-monads.mddocs/

Free Monads

Free monads enable the creation of programs as data structures that can be interpreted later, providing complete separation between program description and execution.

Core API

sealed abstract class Free[S[_], A] extends Product with Serializable

// Construction
def pure[S[_], A](a: A): Free[S, A]
def liftF[F[_], A](value: F[A]): Free[F, A]
def roll[F[_], A](value: F[Free[F, A]]): Free[F, A]
def defer[F[_], A](value: => Free[F, A]): Free[F, A]

// Monadic Operations
def map[B](f: A => B): Free[S, B]
def flatMap[B](f: A => Free[S, B]): Free[S, B]

// Transformation
def mapK[T[_]](f: S ~> T): Free[T, A]
def compile[T[_]](f: FunctionK[S, T]): Free[T, A]

// Execution
def foldMap[M[_]](f: FunctionK[S, M])(implicit M: Monad[M]): M[A]
def run(implicit S: Comonad[S]): A
def runM[M[_]](f: S[Free[S, A]] => M[Free[S, A]])(implicit S: Functor[S], M: Monad[M]): M[A]
def runTailRec(implicit S: Monad[S]): S[A]

// Evaluation
def step: Free[S, A]
def resume(implicit S: Functor[S]): Either[S[Free[S, A]], A]
def go(f: S[Free[S, A]] => Free[S, A])(implicit S: Functor[S]): A
def fold[B](r: A => B, s: S[Free[S, A]] => B)(implicit S: Functor[S]): B

// Injection
def inject[G[_]](implicit ev: InjectK[S, G]): Free[G, A]

Factory Methods

// Natural transformations
def mapK[F[_], G[_]](fk: FunctionK[F, G]): FunctionK[Free[F, ?], Free[G, ?]]
def foldMap[F[_], M[_]: Monad](fk: FunctionK[F, M]): FunctionK[Free[F, ?], M]

// Injection helpers
def inject[F[_], G[_]]: FreeInjectKPartiallyApplied[F, G]
def liftInject[G[_]]: FreeLiftInjectKPartiallyApplied[G]
def injectRoll[F[_], G[_], A](ga: G[Free[F, A]])(implicit I: InjectK[G, F]): Free[F, A]
def match_[F[_], G[_], A](fa: Free[F, A])(implicit F: Functor[F], I: InjectK[G, F]): Option[G[Free[F, A]]]

Type Class Instances

implicit def catsFreeMonadForFree[S[_]]: Monad[Free[S, ?]]
implicit def catsFreeDeferForFree[S[_]]: Defer[Free[S, ?]]
implicit def catsFreeFoldableForFree[F[_]](implicit foldableF: Foldable[F]): Foldable[Free[F, ?]]
implicit def catsFreeTraverseForFree[F[_]](implicit traversableF: Traverse[F]): Traverse[Free[F, ?]]

Basic Usage

Defining an Algebra

// Define your domain-specific operations
sealed trait ConsoleA[A]
case class WriteLine(line: String) extends ConsoleA[Unit]
case class ReadLine() extends ConsoleA[String]

// Create type alias for convenience
type Console[A] = Free[ConsoleA, A]

// Smart constructors
def writeLine(line: String): Console[Unit] = liftF(WriteLine(line))
def readLine(): Console[String] = liftF(ReadLine())

Building Programs

val program: Console[String] = for {
  _    <- writeLine("What's your name?")
  name <- readLine()
  _    <- writeLine(s"Hello, $name!")
} yield name

Creating Interpreters

import cats.Id
import cats.arrow.FunctionK

// Interpreter to Id (for testing)
val testInterpreter = new FunctionK[ConsoleA, Id] {
  def apply[A](fa: ConsoleA[A]): Id[A] = fa match {
    case WriteLine(line) => println(line)
    case ReadLine()      => "Test User"
  }
}

// Interpreter to IO
import cats.effect.IO

val ioInterpreter = new FunctionK[ConsoleA, IO] {
  def apply[A](fa: ConsoleA[A]): IO[A] = fa match {
    case WriteLine(line) => IO(println(line))
    case ReadLine()      => IO(scala.io.StdIn.readLine())
  }
}

Executing Programs

// Run with test interpreter
val testResult: String = program.foldMap(testInterpreter)

// Run with IO interpreter  
val ioResult: IO[String] = program.foldMap(ioInterpreter)

Advanced Usage

Composing Algebras with InjectK

import cats.free.InjectK

sealed trait LogA[A]
case class Log(message: String) extends LogA[Unit]

class ConsoleOps[F[_]](implicit I: InjectK[ConsoleA, F]) {
  def writeLine(line: String): Free[F, Unit] = Free.liftInject[F](WriteLine(line))
  def readLine(): Free[F, String] = Free.liftInject[F](ReadLine())
}

class LogOps[F[_]](implicit I: InjectK[LogA, F]) {
  def log(message: String): Free[F, Unit] = Free.liftInject[F](Log(message))
}

// Use with coproduct
import cats.data.Coproduct
type App[A] = Coproduct[ConsoleA, LogA, A]

def program[F[_]](implicit C: ConsoleOps[F], L: LogOps[F]): Free[F, String] = {
  import C._, L._
  for {
    _    <- log("Starting program")
    _    <- writeLine("Enter name:")
    name <- readLine()
    _    <- log(s"User entered: $name")
  } yield name
}

Stack-Safe Recursion

def factorial(n: BigInt): Free[Id, BigInt] = {
  if (n <= 1) Free.pure(1)
  else factorial(n - 1).map(_ * n)
}

// This will not stack overflow due to Free's trampolining
val result = factorial(100000).run

Performance Considerations

  • Free monads provide stack safety through trampolining
  • Use defer for lazy evaluation of computations
  • Consider mapK for changing the underlying functor efficiently
  • For pure computations, prefer Trampoline (Free over Function0)
  • Use foldMap with stack-safe monads for best performance

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