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.
FreeT is a monad transformer for Free monads, allowing you to layer Free over any other monad for enhanced composition with existing monad stacks.
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]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]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, ?]]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)))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 finalimport 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).valueimport 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}"))
}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)mapK and hoist are efficient ways to transform the underlying monadInstall with Tessl CLI
npx tessl i tessl/maven-org-typelevel--cats-free-2-11