or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-utilities.mdcoproduct-unions.mdgeneric-derivation.mdhlist-collections.mdindex.mdoptics-lenses.mdpoly-typelevel.mdrecords-fields.md
tile.json

coproduct-unions.mddocs/

Coproducts and Union Types

Coproducts in Shapeless provide type-safe sum types representing "one of" relationships. They're the dual concept to HLists, modeling exclusive alternatives rather than sequential combinations. Coproducts enable exhaustive pattern matching and safe union operations with compile-time guarantees.

Capabilities

Core Coproduct Types

The fundamental types for creating and working with type-safe unions.

// Base trait for all coproducts (sum types)
sealed trait Coproduct

// Coproduct constructor - either H or T
sealed trait :+:[+H, +T <: Coproduct] extends Coproduct {
  def eliminate[A](f: H => A, g: T => A): A
}

// Left injection - value is of type H
final case class Inl[+H, +T <: Coproduct](head: H) extends (H :+: T)

// Right injection - value is from remaining alternatives T
final case class Inr[+H, +T <: Coproduct](tail: T) extends (H :+: T)

// Empty coproduct - uninhabited/impossible type
sealed trait CNil extends Coproduct {
  def impossible: Nothing  // Used in exhaustive pattern matching
}

Coproduct Construction

Methods for creating coproduct instances from values.

object Coproduct {
  // Factory class for creating coproduct instances with type class injection
  class MkCoproduct[C <: Coproduct] {
    def apply[T](t: T)(implicit inj: Inject[C, T]): C
  }
  
  // Get MkCoproduct instance
  def apply[C <: Coproduct]: MkCoproduct[C]
  
  // Unsafe construction - builds nested Inr structure
  def unsafeMkCoproduct(length: Int, value: Any): Coproduct
  
  // Unsafe extraction - gets value from nested structure
  def unsafeGet(c: Coproduct): Any
}

// Usage examples:
type StringOrInt = String :+: Int :+: CNil
val str: StringOrInt = Inl("hello")
val num: StringOrInt = Inr(Inl(42))

Injection and Selection

Type-safe injection of values into coproducts and selection from coproducts.

// Inject type I into coproduct C
trait Inject[C <: Coproduct, I] {
  def apply(i: I): C
}

// Select/extract type T from coproduct C  
trait Selector[C <: Coproduct, T] {
  def apply(c: C): Option[T]
}

// Access coproduct alternative at position N
trait At[C <: Coproduct, N <: Nat] {
  type Out
  def apply(c: C): Option[Out]
}

// Get compile-time length of coproduct
trait Length[C <: Coproduct] {
  type Out <: Nat
  def apply(c: C): Out
}

// Usage:
val union: String :+: Int :+: Boolean :+: CNil = Coproduct[String :+: Int :+: Boolean :+: CNil]("test")
val extracted: Option[String] = union.select[String]
val atPos: Option[String] = union.at(Nat._0)

Coproduct Transformations

Operations for mapping and transforming coproduct values.

// Map polymorphic function over coproduct
trait Mapper[F, C <: Coproduct] {
  type Out <: Coproduct
  def apply(f: F, c: C): Out
}

// Unify coproduct to common supertype
trait Unifier[C <: Coproduct] {
  type Out
  def apply(c: C): Out
}

// Extend coproduct with additional type
trait ExtendRight[C <: Coproduct, A] {
  type Out <: Coproduct
  def apply(c: C): Out
}

// Usage example:
object toUpperCase extends Poly1 {
  implicit def caseString = at[String](_.toUpperCase)
  implicit def caseInt = at[Int](_.toString.toUpperCase)
}

val mapped = coproduct.map(toUpperCase)

Pattern Matching and Elimination

Safe pattern matching and value extraction from coproducts.

// Eliminate coproduct by providing handlers for each case
def eliminate[A](c: String :+: Int :+: CNil)(
  stringHandler: String => A,
  intHandler: Int => A
): A

// Fold operation for coproducts
trait Folder[F, C <: Coproduct] {
  type Out
  def apply(f: F, c: C): Out
}

// Usage:
val result = union.eliminate(
  (s: String) => s.length,
  (i: Int) => i * 2,
  (b: Boolean) => if (b) 1 else 0
)

Union Types

Higher-level union type abstractions built on coproducts.

// Union type operations
object Union {
  // Create union from value
  def apply[U <: Coproduct]: UnionInstantiator[U]
  
  // Pattern matching support
  trait UnionMatcher[U <: Coproduct] {
    def apply[A](u: U)(cases: PartialFunction[Any, A]): A
  }
}

// Example usage:
type MyUnion = String :+: Int :+: Boolean :+: CNil

val union1: MyUnion = Union[MyUnion]("hello")
val union2: MyUnion = Union[MyUnion](42)

val result = union1 match {
  case s: String => s.length
  case i: Int => i * 2  
  case b: Boolean => if (b) 1 else 0
}

Coproduct Operations

Additional operations for working with coproduct structures.

// Remove type T from coproduct C
trait Remove[C <: Coproduct, T] {
  type Out <: Coproduct
  def apply(c: C): Either[T, Out]
}

// Split coproduct at position N
trait Split[C <: Coproduct, N <: Nat] {
  type Prefix <: Coproduct
  type Suffix <: Coproduct
  def apply(c: C): Either[Prefix, Suffix]
}

// Reverse coproduct structure
trait Reverse[C <: Coproduct] {
  type Out <: Coproduct
  def apply(c: C): Out
}

// Rotate coproduct alternatives
trait RotateLeft[C <: Coproduct, N <: Nat] {
  type Out <: Coproduct
  def apply(c: C): Out
}

Integration with HLists

Operations that work between coproducts and HLists.

// Apply HList of functions to coproduct
trait ZipApply[L <: HList, C <: Coproduct] {
  type Out <: Coproduct
  def apply(fl: L, c: C): Out
}

// Convert between HList and Coproduct representations
trait ToHList[C <: Coproduct] {
  type Out <: HList
  def apply(c: C): Out
}

trait FromHList[L <: HList] {
  type Out <: Coproduct
  def apply(l: L): Out
}

Usage Examples

Basic Coproduct Operations

import shapeless._

// Define coproduct type
type Response = String :+: Int :+: Boolean :+: CNil

// Create instances
val stringResponse: Response = Inl("success")
val intResponse: Response = Inr(Inl(200))  
val boolResponse: Response = Inr(Inr(Inl(true)))

// Pattern match and extract
val message = stringResponse.select[String]  // Some("success")
val code = intResponse.select[Int]          // Some(200)
val flag = stringResponse.select[Boolean]   // None

// Eliminate with handlers
val result = stringResponse.eliminate(
  (s: String) => s"Message: $s",
  (i: Int) => s"Code: $i", 
  (b: Boolean) => s"Flag: $b"
)

Polymorphic Function Mapping

object processResponse extends Poly1 {
  implicit def caseString = at[String](s => s"Processed: $s")
  implicit def caseInt = at[Int](i => s"Status: $i")
  implicit def caseBoolean = at[Boolean](b => s"Active: $b")
}

val processed = stringResponse.map(processResponse)
// Results in String :+: Int :+: Boolean :+: CNil with transformed value

Generic Coproduct Derivation

sealed trait Color
case object Red extends Color
case object Green extends Color  
case object Blue extends Color

// Automatically derive coproduct representation
val gen = Generic[Color]
type ColorRepr = gen.Repr  // Red.type :+: Green.type :+: Blue.type :+: CNil

val red: Color = Red
val repr = gen.to(red)     // Coproduct representation
val back = gen.from(repr)  // Back to Color

Safe Union Operations

// Define union of possible error types
sealed trait DatabaseError
case class ConnectionError(message: String) extends DatabaseError
case class QueryError(sql: String, error: String) extends DatabaseError
case class TimeoutError(duration: Int) extends DatabaseError

type ErrorUnion = ConnectionError :+: QueryError :+: TimeoutError :+: CNil

def handleError(error: ErrorUnion): String = error.eliminate(
  (ce: ConnectionError) => s"Connection failed: ${ce.message}",
  (qe: QueryError) => s"Query failed: ${qe.sql} - ${qe.error}",
  (te: TimeoutError) => s"Timeout after ${te.duration}ms"
)

// Type-safe injection
val connectionError: ErrorUnion = Coproduct[ErrorUnion](ConnectionError("Network unreachable"))
val message = handleError(connectionError)

Runtime Type Switching

def processValue(value: Any): Option[String :+: Int :+: Boolean :+: CNil] = {
  value match {
    case s: String => Some(Inl(s))
    case i: Int => Some(Inr(Inl(i)))
    case b: Boolean => Some(Inr(Inr(Inl(b))))
    case _ => None
  }
}

val result = processValue("hello")
// Some(Inl("hello")) with type Option[String :+: Int :+: Boolean :+: CNil]