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-transformer.mddocs/

Free Monad Transformer

FreeT is a monad transformer for Free monads, allowing you to layer Free over any other monad for enhanced composition with existing monad stacks.

Core API

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

// Construction
def pure[S[_], M[_], A](value: A)(implicit M: Applicative[M]): FreeT[S, M, A]
def liftF[S[_], M[_], A](value: S[A])(implicit M: Applicative[M]): FreeT[S, M, A]
def liftT[S[_], M[_], A](value: M[A])(implicit M: Functor[M]): FreeT[S, M, A]
def roll[S[_], M[_], A](value: S[FreeT[S, M, A]])(implicit M: Applicative[M]): FreeT[S, M, A]
def defer[S[_], M[_], A](a: M[Either[A, S[FreeT[S, M, A]]]])(implicit M: Applicative[M]): FreeT[S, M, A]

// Monadic Operations
def map[B](f: A => B)(implicit M: Applicative[M]): FreeT[S, M, B]
def flatMap[B](f: A => FreeT[S, M, B]): FreeT[S, M, B]

// Transformations
def mapK[N[_]](mn: M ~> N): FreeT[S, N, A]
def hoist[N[_]](mn: FunctionK[M, N]): FreeT[S, N, A]
def compile[T[_]](st: FunctionK[S, T])(implicit M: Functor[M]): FreeT[T, M, A]

// Execution
def foldMap(f: FunctionK[S, M])(implicit M: Monad[M]): M[A]
def resume(implicit S: Functor[S], M: Monad[M]): M[Either[S[FreeT[S, M, A]], A]]
def runM(interp: S[FreeT[S, M, A]] => M[FreeT[S, M, A]])(implicit S: Functor[S], M: Monad[M]): M[A]

Factory Methods

def tailRecM[S[_], M[_]: Applicative, A, B](a: A)(f: A => FreeT[S, M, Either[A, B]]): FreeT[S, M, B]

// Natural transformations
def compile[S[_], T[_], M[_]: Functor](st: FunctionK[S, T]): FunctionK[FreeT[S, M, ?], FreeT[T, M, ?]]
def foldMap[S[_], M[_]: Monad](fk: FunctionK[S, M]): FunctionK[FreeT[S, M, ?], M]

// Injection helpers
def liftInject[M[_], G[_]]: FreeTLiftInjectKPartiallyApplied[M, G]

Type Class Instances

implicit def catsFreeMonadErrorForFreeT[S[_], M[_], E](implicit E: MonadError[M, E]): MonadError[FreeT[S, M, ?], E]
implicit def catsFreeMonadForFreeT[S[_], M[_]](implicit M0: Applicative[M]): Monad[FreeT[S, M, ?]]
implicit def catsFreeFlatMapForFreeT[S[_], M[_]](implicit M0: Applicative[M]): FlatMap[FreeT[S, M, ?]]
implicit def catsFreeAlternativeForFreeT[S[_], M[_]: Alternative: Monad]: Alternative[FreeT[S, M, ?]]
implicit def catsFreeSemigroupKForFreeT[S[_], M[_]: Applicative: SemigroupK]: SemigroupK[FreeT[S, M, ?]]

Basic Usage

Simple State + Free Combination

import cats.data.State
import cats.implicits._

sealed trait LogA[A]
case class LogInfo(msg: String) extends LogA[Unit]
case class LogError(msg: String) extends LogA[Unit]

type AppState = Map[String, Int]
type StateT[A] = State[AppState, A]
type App[A] = FreeT[LogA, StateT, A]

def logInfo(msg: String): App[Unit] = FreeT.liftF[LogA, StateT, Unit](LogInfo(msg))
def logError(msg: String): App[Unit] = FreeT.liftF[LogA, StateT, Unit](LogError(msg))
def getCount(key: String): App[Int] = FreeT.liftT(State.get[AppState].map(_.getOrElse(key, 0)))
def setCount(key: String, value: Int): App[Unit] = FreeT.liftT(State.modify[AppState](_ + (key -> value)))

Building Programs

val program: App[Int] = for {
  _     <- logInfo("Starting computation")
  count <- getCount("counter")
  _     <- logInfo(s"Current count: $count")
  _     <- setCount("counter", count + 1)
  _     <- logInfo("Incremented counter")
  final <- getCount("counter")
} yield final

Creating Interpreters

import cats.Id
import cats.arrow.FunctionK

// Interpreter for LogA
val logInterpreter = new FunctionK[LogA, StateT] {
  def apply[A](fa: LogA[A]): StateT[A] = fa match {
    case LogInfo(msg)  => State.pure(println(s"INFO: $msg"))
    case LogError(msg) => State.pure(println(s"ERROR: $msg"))
  }
}

// Run the program
val initialState = Map.empty[String, Int]
val (finalState, result) = program.foldMap(logInterpreter).run(initialState).value

Advanced Usage

Error Handling with MonadError

import cats.effect.IO
import cats.MonadError

sealed trait FileA[A]
case class ReadFile(path: String) extends FileA[String]
case class WriteFile(path: String, content: String) extends FileA[Unit]

type FileApp[A] = FreeT[FileA, IO, A]

def readFile(path: String): FileApp[String] = FreeT.liftF[FileA, IO, String](ReadFile(path))
def writeFile(path: String, content: String): FileApp[Unit] = FreeT.liftF[FileA, IO, Unit](WriteFile(path, content))

// Error handling interpreter
val fileInterpreter = new FunctionK[FileA, IO] {
  def apply[A](fa: FileA[A]): IO[A] = fa match {
    case ReadFile(path) => IO {
      scala.io.Source.fromFile(path).getLines().mkString("\n")
    }.handleErrorWith(e => IO.raiseError(new RuntimeException(s"Failed to read $path: ${e.getMessage}")))
    
    case WriteFile(path, content) => IO {
      val writer = new java.io.PrintWriter(path)
      try writer.write(content) finally writer.close()
    }.handleErrorWith(e => IO.raiseError(new RuntimeException(s"Failed to write $path: ${e.getMessage}")))
  }
}

// Program with error handling
val fileProgram: FileApp[String] = for {
  content <- readFile("input.txt")
  _       <- writeFile("output.txt", content.toUpperCase)
  result  <- readFile("output.txt")
} yield result

// The FreeT inherits MonadError capabilities from IO
val safeProgram = fileProgram.handleErrorWith { error =>
  FreeT.liftT(IO.pure(s"Error occurred: ${error.getMessage}"))
}

Composing Multiple Effect Types

import cats.data.OptionT

// Stack: FreeT over OptionT over IO
type OptionalApp[A] = FreeT[LogA, OptionT[IO, ?], A]

def logOptional(msg: String): OptionalApp[Unit] = 
  FreeT.liftF[LogA, OptionT[IO, ?], Unit](LogInfo(msg))

def maybeValue(condition: Boolean): OptionalApp[String] =
  FreeT.liftT(OptionT.fromOption[IO](if (condition) Some("success") else None))

val optionalProgram: OptionalApp[String] = for {
  _      <- logOptional("Checking condition")
  result <- maybeValue(true)
  _      <- logOptional(s"Got result: $result")
} yield result

// Interpreter that preserves the OptionT structure
val optionalLogInterpreter = new FunctionK[LogA, OptionT[IO, ?]] {
  def apply[A](fa: LogA[A]): OptionT[IO, A] = fa match {
    case LogInfo(msg)  => OptionT.liftF(IO(println(s"INFO: $msg")))
    case LogError(msg) => OptionT.liftF(IO(println(s"ERROR: $msg")))
  }
}

val result: OptionT[IO, String] = optionalProgram.foldMap(optionalLogInterpreter)

Performance Considerations

  • FreeT adds monad transformer overhead - use judiciously
  • Consider using regular Free when you don't need the additional monad layer
  • mapK and hoist are efficient ways to transform the underlying monad
  • Stack safety is preserved from the underlying Free implementation

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