or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

cofree.mdfree-applicative.mdfree-monads.mdfree-transformer.mdindex.mdinterpreters.mdtrampoline.mdyoneda-coyoneda.md
tile.json

free-applicative.mddocs/

Free Applicative

Free applicative functors enable building parallel, analyzable computations that don't require the sequential binding power of monads, allowing for better optimization and parallelization.

Core API

sealed abstract class FreeApplicative[F[_], A] extends Product with Serializable

// Construction
def pure[F[_], A](a: A): FreeApplicative[F, A]
def lift[F[_], A](fa: F[A]): FreeApplicative[F, A]

// Applicative Operations
def ap[B](b: FreeApplicative[F, A => B]): FreeApplicative[F, B]
def map[B](f: A => B): FreeApplicative[F, B]
def map2[B, C](fb: FreeApplicative[F, B])(f: (A, B) => C): FreeApplicative[F, C]

// Execution and Analysis
def foldMap[G[_]](f: F ~> G)(implicit G: Applicative[G]): G[A]

Factory Methods

// Natural transformations
def compile[F[_], G[_]](f: FunctionK[F, G]): FunctionK[FreeApplicative[F, ?], FreeApplicative[G, ?]]
def foldMap[F[_], G[_]: Applicative](f: FunctionK[F, G]): FunctionK[FreeApplicative[F, ?], G]
def analyze[F[_], M: Monoid](f: FunctionK[F, λ[α => M]]): FunctionK[FreeApplicative[F, ?], λ[α => M]]

Type Class Instances

implicit def catsFreeApplicativeForFreeApplicative[F[_]]: Applicative[FreeApplicative[F, ?]]

Basic Usage

Validation Example

import cats.data.{NonEmptyList, Validated}
import cats.implicits._

sealed trait ValidationA[A]
case class CheckNotEmpty(field: String, value: String) extends ValidationA[String]
case class CheckMinLength(field: String, value: String, min: Int) extends ValidationA[String]
case class CheckEmail(field: String, value: String) extends ValidationA[String]

type Validation[A] = FreeApplicative[ValidationA, A]

def checkNotEmpty(field: String, value: String): Validation[String] = 
  FreeApplicative.lift(CheckNotEmpty(field, value))

def checkMinLength(field: String, value: String, min: Int): Validation[String] = 
  FreeApplicative.lift(CheckMinLength(field, value, min))

def checkEmail(field: String, value: String): Validation[String] = 
  FreeApplicative.lift(CheckEmail(field, value))

Building Validation Programs

case class User(name: String, email: String, password: String)

def validateUser(name: String, email: String, password: String): Validation[User] = {
  val validName = checkNotEmpty("name", name)
  val validEmail = (checkNotEmpty("email", email), checkEmail("email", email)).mapN((_, email) => email)
  val validPassword = (checkNotEmpty("password", password), checkMinLength("password", password, 8)).mapN((_, pwd) => pwd)
  
  (validName, validEmail, validPassword).mapN(User.apply)
}

Creating Interpreters

import cats.arrow.FunctionK

type ValidationResult[A] = Validated[NonEmptyList[String], A]

val validationInterpreter = new FunctionK[ValidationA, ValidationResult] {
  def apply[A](fa: ValidationA[A]): ValidationResult[A] = fa match {
    case CheckNotEmpty(field, value) =>
      if (value.nonEmpty) Validated.valid(value)
      else Validated.invalid(NonEmptyList.one(s"$field cannot be empty"))
      
    case CheckMinLength(field, value, min) =>
      if (value.length >= min) Validated.valid(value)
      else Validated.invalid(NonEmptyList.one(s"$field must be at least $min characters"))
      
    case CheckEmail(field, value) =>
      if (value.contains("@")) Validated.valid(value)
      else Validated.invalid(NonEmptyList.one(s"$field must be a valid email"))
  }
}

// Execute validation
val userValidation = validateUser("Alice", "alice@example.com", "secret123")
val result: ValidationResult[User] = userValidation.foldMap(validationInterpreter)

Advanced Usage

Analysis and Optimization

// Analyze validation structure
val analyzeInterpreter = new FunctionK[ValidationA, λ[α => List[String]]] {
  def apply[A](fa: ValidationA[A]): List[String] = fa match {
    case CheckNotEmpty(field, _) => List(s"empty-check:$field")
    case CheckMinLength(field, _, min) => List(s"length-check:$field:$min")
    case CheckEmail(field, _) => List(s"email-check:$field")
  }
}

val checks: List[String] = userValidation.analyze(analyzeInterpreter)
// Result: List("empty-check:name", "empty-check:email", "email-check:email", "empty-check:password", "length-check:password:8")

Parallel Execution

import cats.effect.IO
import cats.Parallel

// Interpreter that supports parallel execution
val asyncValidationInterpreter = new FunctionK[ValidationA, IO] {
  def apply[A](fa: ValidationA[A]): IO[A] = fa match {
    case CheckNotEmpty(field, value) =>
      IO {
        Thread.sleep(100) // Simulate async work
        if (value.nonEmpty) value else throw new RuntimeException(s"$field is empty")
      }
      
    case CheckMinLength(field, value, min) =>
      IO {
        Thread.sleep(100) // Simulate async work  
        if (value.length >= min) value else throw new RuntimeException(s"$field too short")
      }
      
    case CheckEmail(field, value) =>
      IO {
        Thread.sleep(100) // Simulate async work
        if (value.contains("@")) value else throw new RuntimeException(s"$field invalid email")
      }
  }
}

// This will execute validations in parallel when possible
val asyncResult: IO[User] = userValidation.foldMap(asyncValidationInterpreter)

Form Field Validation

// More complex example with multiple field types
sealed trait FormA[A]
case class ValidateRequired[T](field: String, value: Option[T]) extends FormA[T]
case class ValidateRange(field: String, value: Int, min: Int, max: Int) extends FormA[Int]
case class ValidatePattern(field: String, value: String, pattern: String) extends FormA[String]

type Form[A] = FreeApplicative[FormA, A]

def required[T](field: String, value: Option[T]): Form[T] = 
  FreeApplicative.lift(ValidateRequired(field, value))

def range(field: String, value: Int, min: Int, max: Int): Form[Int] = 
  FreeApplicative.lift(ValidateRange(field, value, min, max))

def pattern(field: String, value: String, regex: String): Form[String] = 
  FreeApplicative.lift(ValidatePattern(field, value, regex))

case class Registration(name: String, age: Int, email: String, phone: String)

def validateRegistration(
  name: Option[String], 
  age: Option[Int], 
  email: Option[String], 
  phone: Option[String]
): Form[Registration] = {
  val validName = required("name", name)
  val validAge = required("age", age).ap(range("age", age.getOrElse(0), 13, 120).map(Function.const))
  val validEmail = required("email", email).ap(pattern("email", email.getOrElse(""), ".*@.*").map(Function.const))
  val validPhone = required("phone", phone).ap(pattern("phone", phone.getOrElse(""), "\\d{10}").map(Function.const))
  
  (validName, validAge, validEmail, validPhone).mapN(Registration.apply)
}

Performance Considerations

  • Free applicatives allow for better parallelization than Free monads
  • Use analyze to inspect and optimize computation structure before execution
  • The applicative nature enables static analysis and optimization
  • Consider Free applicative for validation, configuration parsing, and other independent computations
  • Stack-safe execution through trampolining in foldMap