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

advanced-utilities.mddocs/

Advanced Features and Utilities

Shapeless provides specialized features including functional cursors (zippers), tuple operations, compile-time testing utilities, annotation processing, runtime type information, and various conversion utilities for integration with Scala's standard library.

Capabilities

Zipper - Functional Cursors

Functional data structure for traversing and modifying HLists with positional context.

// Generic Zipper for any type with a representation via Generic
case class Zipper[C, L <: HList, R <: HList, P](prefix: L, suffix: R, parent: P) {
  // Move cursor operations with type class constraints
  def right(implicit right: Right[Self]): right.Out
  def left(implicit left: Left[Self]): left.Out
  def first(implicit first: First[Self]): first.Out
  def last(implicit last: Last[Self]): last.Out
  
  // Move by n positions
  def rightBy[N <: Nat](implicit rightBy: RightBy[Self, N]): rightBy.Out
  def leftBy[N <: Nat](implicit leftBy: LeftBy[Self, N]): leftBy.Out
  
  // Modification operations
  def put[A](a: A)(implicit put: Put[Self, A]): put.Out
  def insert[A](a: A)(implicit insert: Insert[Self, A]): insert.Out
  def delete(implicit delete: Delete[Self]): delete.Out
  
  // Navigation in nested structures
  def down(implicit down: Down[Self]): down.Out
  def up(implicit up: Up[Self]): up.Out
  def root(implicit root: Root[Self]): root.Out
}

// Zipper creation
trait HListZipper[L <: HList] {
  type Out
  def apply(l: L): Out
}

object Zipper {
  // Create zipper focused on first element
  def apply[L <: HList](l: L)(implicit zipper: HListZipper[L]): zipper.Out
}

Zipper Operations

Type classes for zipper navigation and modification.

// Move cursor to the right (towards tail)
trait Right[Z] {
  type Out
  def apply(z: Z): Option[Out]
}

// Move cursor to the left (towards head)  
trait Left[Z] {
  type Out
  def apply(z: Z): Option[Out]
}

// Move to first element
trait First[Z] {
  type Out
  def apply(z: Z): Out
}

// Move to last element
trait Last[Z] {
  type Out  
  def apply(z: Z): Out
}

// Get element at cursor
trait Get[Z] {
  type Out
  def apply(z: Z): Out  
}

// Replace element at cursor with A
trait Put[Z, A] {
  type Out
  def apply(z: Z, a: A): Out
}

// Insert element A at cursor
trait Insert[Z, A] {
  type Out
  def apply(z: Z, a: A): Out
}

// Delete element at cursor
trait Delete[Z] {
  type Out
  def apply(z: Z): Option[Out]
}

Tuple Operations

Enhanced operations for working with Scala tuples through HList conversion.

// Witnesses tuple P can be decomposed
trait IsComposite[P] {
  type H
  type T
  def head(p: P): H
  def tail(p: P): T
}

// All but last element of tuple
trait Init[T] {
  type Out
  def apply(t: T): Out
}

// Last element of tuple
trait Last[T] {
  type Out
  def apply(t: T): Out
}

// Prepend element to tuple
trait Prepend[P, T] {
  type Out
  def apply(p: P, t: T): Out
}

// Reverse first tuple and prepend to second
trait ReversePrepend[P, Q] {
  type Out
  def apply(p: P, q: Q): Out
}

Compile-Time Testing Utilities

Tools for compile-time verification and type checking.

// Assert expression has type T
def typed[T](t: => T): Unit = {}

// Assert two expressions have same type T  
def sameTyped[T](t1: => T)(t2: => T): Unit = {}

// Display type T at compile time (compiler output)
def showType[T]: Unit = {}

// Display type bounds for T
def showBounds[T]: Unit = {}

// Assert code in string t fails to type check
def typeError(t: String): Unit = macro TestMacros.typeError

// Assert code in string t fails to compile  
def compileError(t: String): Unit = macro TestMacros.compileError

// Deprecated alias for typeError
def illTyped(t: String): Unit = macro TestMacros.illTyped

// Usage:
typed[String]("hello")              // Compiles
typed[Int]("hello")                 // Compile error
sameTyped(1)(2)                     // Compiles (both Int)
sameTyped("a")(1)                   // Compile error
typeError("val x: String = 123")    // Succeeds (code doesn't type check)

Runtime Type Information (Typeable)

Enhanced runtime type information beyond Scala's built-in TypeTag.

// Enhanced runtime type information
trait Typeable[T] {
  // Safe casting with runtime type checking
  def cast(any: Any): Option[T]
  
  // Describe the type at runtime
  def describe: String
  
  // Type equality checking at runtime
  def =:=[U](other: Typeable[U]): Boolean
}

object Typeable {
  // Get Typeable instance
  def apply[T](implicit typeable: Typeable[T]): Typeable[T] = typeable
  
  // Built-in instances for primitive types
  implicit val intTypeable: Typeable[Int]
  implicit val stringTypeable: Typeable[String]
  implicit val booleanTypeable: Typeable[Boolean]
  
  // Generic derivation for case classes and sealed traits
  implicit def genericTypeable[T](implicit 
    gen: Generic[T],
    reprTypeable: Lazy[Typeable[gen.Repr]]
  ): Typeable[T]
}

Annotation Support

Compile-time annotation access and processing.

// Extract annotation A from type T
trait Annotation[T, A] {
  def apply(): A
}

// Extract all annotations A from type T  
trait Annotations[T, A] {
  def apply(): List[A]
}

object Annotation {
  // Get annotation instance
  def apply[T, A](implicit ann: Annotation[T, A]): A = ann()
}

// Usage with custom annotations:
class MyAnnotation(value: String) extends scala.annotation.StaticAnnotation

@MyAnnotation("example")
case class AnnotatedClass(field: String)

val annotation = Annotation[AnnotatedClass, MyAnnotation]
// Returns MyAnnotation("example")

Default Value Derivation

Automatic derivation of default values for types.

// Provides default values for types T
trait Default[T] {
  def apply(): T
}

object Default {
  // Get default instance
  def apply[T](implicit default: Default[T]): T = default()
  
  // Built-in defaults
  implicit val intDefault: Default[Int] = () => 0
  implicit val stringDefault: Default[String] = () => ""
  implicit val booleanDefault: Default[Boolean] = () => false
  
  // Option defaults to None
  implicit def optionDefault[T]: Default[Option[T]] = () => None
  
  // List defaults to empty
  implicit def listDefault[T]: Default[List[T]] = () => Nil
  
  // Automatic derivation for case classes
  implicit def genericDefault[A, L <: HList](implicit
    gen: Generic.Aux[A, L],
    defaults: Default[L]
  ): Default[A] = () => gen.from(defaults())
}

Alternative Resolution (OrElse)

Alternative implicit resolution with fallback behavior.

// Try A, fallback to B if A not available
trait OrElse[A, B] {
  type Out
  def apply(): Out
}

object OrElse {
  // Prefer primary if available
  implicit def primary[A, B](implicit a: A): OrElse[A, B] = 
    new OrElse[A, B] { 
      type Out = A
      def apply() = a 
    }
  
  // Fallback to secondary
  implicit def secondary[A, B](implicit b: B): OrElse[A, B] = 
    new OrElse[A, B] {
      type Out = B  
      def apply() = b
    }
}

// Usage for providing default implementations
trait Show[T] { def show(t: T): String }
trait PrettyShow[T] { def prettyShow(t: T): String }

def display[T](t: T)(implicit showable: OrElse[PrettyShow[T], Show[T]]): String = {
  showable() match {
    case pretty: PrettyShow[T] => pretty.prettyShow(t)
    case show: Show[T] => show.show(t)
  }
}

Refutation - Negative Reasoning

Witnesses the absence of implicit instances for negative reasoning about types.

// Witnesses the absence of implicit instances of type T
trait Refute[T]

object Refute {
  // Evidence that T is not available as implicit
  implicit def refute[T](implicit dummy: Refute.Dummy): Refute[T] = 
    new Refute[T] {}
    
  // Fails if T is available as implicit  
  implicit def ambiguous1[T](implicit t: T): Refute[T] = ???
  implicit def ambiguous2[T](implicit t: T): Refute[T] = ???
  
  trait Dummy
  implicit val dummy: Dummy = new Dummy {}
}

// Usage: 
def requireNoShow[T](implicit refute: Refute[Show[T]]): String = 
  "No Show instance available for T"

Unwrapped Types

Automatic unwrapping of newtype/tagged wrapper types.

// Unwrapper from wrapper W to underlying U
trait Unwrapped[W] {
  type U
  def unwrap(w: W): U
}

object Unwrapped {
  // Type alias with fixed underlying type
  type Aux[W, U0] = Unwrapped[W] { type U = U0 }
  
  // Unwrap tagged types  
  implicit def taggedUnwrapped[T, Tag]: Unwrapped.Aux[T @@ Tag, T] = 
    new Unwrapped[T @@ Tag] {
      type U = T
      def unwrap(tagged: T @@ Tag): T = tagged
    }
}

Standard Library Extensions

Extensions and integrations with Scala's standard library.

// Either extensions
implicit class EitherOps[A, B](either: Either[A, B]) {
  def toHList: A :+: B :+: CNil = either match {
    case Left(a) => Inl(a)
    case Right(b) => Inr(Inl(b))
  }
}

// Function extensions  
implicit class FunctionOps[A, B](f: A => B) {
  def toPolyTrain: Poly1 = new Poly1 {
    implicit def cse = at[A](f)
  }
}

// Map extensions
implicit class MapOps[K, V](map: Map[K, V]) {
  def toRecord: HList = ??? // Convert to record representation
}

// Traversable extensions
implicit class TraversableOps[A](trav: Traversable[A]) {
  def toHList: HList = ??? // Convert to HList if homogeneous
}

Usage Examples

Zipper Navigation

import shapeless._, zipper._

val data = "first" :: 2 :: true :: "last" :: HNil

// Create zipper focused on first element
val zipper = data.toZipper
// Zipper focused on "first"

// Navigate
val moved = zipper.right.right  // Now focused on true
val atEnd = moved.right         // Now focused on "last"  
val backToFirst = atEnd.first   // Back to "first"

// Modify at cursor
val modified = moved.put(false)  // Replace true with false
val inserted = moved.insert("new") // Insert "new" before true
val deleted = moved.delete       // Remove true

// Convert back to HList
val result = modified.toHList
// "first" :: 2 :: false :: "last" :: HNil

Runtime Type Checking

import shapeless._, typeable._

def safeCast[T: Typeable](any: Any): Option[T] = 
  Typeable[T].cast(any)

// Safe casting with runtime checks
val value: Any = "hello world"

val asString = safeCast[String](value)     // Some("hello world")
val asInt = safeCast[Int](value)           // None
val asList = safeCast[List[String]](value) // None

// Type descriptions
val stringDesc = Typeable[String].describe        // "String"
val listDesc = Typeable[List[Int]].describe       // "List[Int]"
val complexDesc = Typeable[(String, Option[Int])].describe // "(String, Option[Int])"

Compile-Time Testing

import shapeless._, test._

// Verify types at compile time
typed[String]("hello")                    // ✓ Compiles
typed[Int](42)                           // ✓ Compiles  
// typed[String](42)                     // ✗ Compile error

// Verify same types
sameTyped(List(1, 2, 3))(List(4, 5, 6))  // ✓ Both List[Int]
// sameTyped(List(1, 2, 3))("hello")     // ✗ Different types

// Verify code fails to compile
typeError("val x: String = 123")          // ✓ Code doesn't type check
typeError("val y: Int = 456")             // ✗ Code does type check

// Debug types at compile time  
showType[List[String]]                     // Prints type to compiler output
showBounds[List[_]]                        // Shows type bounds

Default Value Generation

import shapeless._, Default._

case class Config(
  host: String,
  port: Int, 
  ssl: Boolean,
  timeout: Option[Int],
  retries: List[Int]
)

// Automatic default derivation
implicit val hostDefault: Default[String] = () => "localhost"
implicit val portDefault: Default[Int] = () => 8080
implicit val sslDefault: Default[Boolean] = () => false

val defaultConfig = Default[Config].apply()
// Config("localhost", 8080, false, None, List())

// Manual defaults
case class Person(name: String, age: Int = 0, active: Boolean = true)

val defaultPerson = Default[Person].apply()
// Uses field defaults where available

Annotation Processing

import shapeless._, annotation._

// Custom annotations
class ApiDoc(description: String) extends scala.annotation.StaticAnnotation
class Deprecated(since: String) extends scala.annotation.StaticAnnotation

@ApiDoc("User information")
case class User(
  @ApiDoc("Full name") name: String,
  @ApiDoc("Age in years") @Deprecated("2.0") age: Int,
  @ApiDoc("Account status") active: Boolean
)

// Extract annotations
val classDoc = Annotation[User, ApiDoc]
// ApiDoc("User information")

val fieldDocs = Annotations[User, ApiDoc]
// List(ApiDoc("Full name"), ApiDoc("Age in years"), ApiDoc("Account status"))

val deprecations = Annotations[User, Deprecated]  
// List(Deprecated("2.0"))

Alternative Resolution

import shapeless._, OrElse._

// Preference system for formatting
trait JsonFormat[T] { def toJson(t: T): String }
trait SimpleFormat[T] { def format(t: T): String }

implicit val intJson: JsonFormat[Int] = i => s"""{"value": $i}"""
implicit val stringSimple: SimpleFormat[String] = s => s""""$s""""

def formatValue[T](t: T)(implicit 
  formatter: OrElse[JsonFormat[T], SimpleFormat[T]]
): String = {
  formatter() match {
    case json: JsonFormat[T] => json.toJson(t)
    case simple: SimpleFormat[T] => simple.format(t)  
  }
}

val intFormatted = formatValue(42)      // Uses JsonFormat: {"value": 42}
val stringFormatted = formatValue("hi") // Uses SimpleFormat: "hi"

Unwrapping Tagged Types

import shapeless._, tag._, Unwrapped._

// Tagged types for domain modeling
trait UserId
trait Email  

type UserIdValue = String @@ UserId
type EmailValue = String @@ Email

val userId: UserIdValue = tag[UserId]("user123")
val email: EmailValue = tag[Email]("test@example.com")

// Automatic unwrapping
def processId[T](wrapped: T)(implicit 
  unwrapped: Unwrapped[T]
): unwrapped.U = unwrapped.unwrap(wrapped)

val rawUserId: String = processId(userId)  // "user123"
val rawEmail: String = processId(email)    // "test@example.com"

// Type-safe unwrapping preserves information
def logValue[W](wrapped: W)(implicit 
  unwrapped: Unwrapped.Aux[W, String]
): Unit = println(s"Value: ${unwrapped.unwrap(wrapped)}")

logValue(userId)  // "Value: user123"
logValue(email)   // "Value: test@example.com"