CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-chuusai--shapeless-2-11

An exploration of generic (aka polytypic) programming in Scala derived from implementing scrap your boilerplate and higher rank polymorphism patterns

Pending
Overview
Eval results
Files

typeable.mddocs/

Runtime Type Safety

The Typeable type class in shapeless provides runtime type-safe casting with compile-time guarantees. It enables safe downcasting and type checking operations while maintaining the benefits of static typing.

Core Type Class

Typeable Definition

/**
 * Supports runtime type-safe casting for type U
 */
trait Typeable[U] {
  /**
   * Attempt to cast Any value to type U
   * @param t value to cast
   * @return Some(u) if cast succeeds, None if it fails
   */
  def cast(t: Any): Option[U]
}

Enhanced Casting Operations

Cast Enhancement

/**
 * Provides type-safe casting operations for any value
 */
class Cast(t: Any) {
  /**
   * Attempt to cast to type U
   */
  def cast[U](implicit castU: Typeable[U]): Option[U] = castU.cast(t)
}

/**
 * Implicit conversion to enable .cast syntax on any value
 */
implicit def anyCast(t: Any): Cast = new Cast(t)

Usage Examples:

import shapeless._

val anyValue: Any = 42
val stringValue: Any = "hello"  
val listValue: Any = List(1, 2, 3)

// Safe casting with Option results
val asInt: Option[Int] = anyValue.cast[Int]           // Some(42)
val asString: Option[String] = anyValue.cast[String]  // None
val asList: Option[List[Int]] = listValue.cast[List[Int]] // Some(List(1, 2, 3))

// Use in pattern matching
anyValue.cast[Int] match {
  case Some(i) => println(s"Got integer: $i")
  case None => println("Not an integer")
}

// Chain operations safely
val result = for {
  str <- stringValue.cast[String]
  if str.length > 3
} yield str.toUpperCase
// result: Option[String] = Some("HELLO")

Provided Instances

Primitive Types

Shapeless provides Typeable instances for all primitive types:

// Numeric primitives
implicit val byteTypeable: Typeable[Byte]
implicit val shortTypeable: Typeable[Short] 
implicit val charTypeable: Typeable[Char]
implicit val intTypeable: Typeable[Int]
implicit val longTypeable: Typeable[Long]
implicit val floatTypeable: Typeable[Float]
implicit val doubleTypeable: Typeable[Double]
implicit val booleanTypeable: Typeable[Boolean]
implicit val unitTypeable: Typeable[Unit]

Usage Examples:

import shapeless._

val mixedValues: List[Any] = List(42, 3.14, true, "hello", 'c')

// Filter by type
val integers = mixedValues.flatMap(_.cast[Int])      // List(42)
val doubles = mixedValues.flatMap(_.cast[Double])    // List(3.14)  
val booleans = mixedValues.flatMap(_.cast[Boolean])  // List(true)
val chars = mixedValues.flatMap(_.cast[Char])        // List('c')

// Type-safe processing
def processNumeric(value: Any): Option[Double] = {
  value.cast[Int].map(_.toDouble)
    .orElse(value.cast[Double])
    .orElse(value.cast[Float].map(_.toDouble))
    .orElse(value.cast[Long].map(_.toDouble))
}

val numericResults = mixedValues.flatMap(processNumeric)
// List(42.0, 3.14)

Hierarchy Types

// Type hierarchy roots
implicit val anyValTypeable: Typeable[AnyVal]
implicit val anyRefTypeable: Typeable[AnyRef]

Usage Examples:

import shapeless._

val values: List[Any] = List(42, "hello", true, List(1, 2), 3.14)

// Separate value types from reference types
val valueTypes = values.flatMap(_.cast[AnyVal])     // List(42, true, 3.14)
val referenceTypes = values.flatMap(_.cast[AnyRef]) // List("hello", List(1, 2))

// Check type categories
def categorizeValue(value: Any): String = {
  if (value.cast[AnyVal].isDefined) "Value type"
  else if (value.cast[AnyRef].isDefined) "Reference type"  
  else "Unknown type"
}

val categories = values.map(categorizeValue)
// List("Value type", "Reference type", "Value type", "Reference type", "Value type")

Generic Container Types

// Option types
implicit def optionTypeable[T: Typeable]: Typeable[Option[T]]

// Either types  
implicit def eitherTypeable[A: Typeable, B: Typeable]: Typeable[Either[A, B]]
implicit def leftTypeable[A: Typeable, B: Typeable]: Typeable[Left[A, B]]
implicit def rightTypeable[A: Typeable, B: Typeable]: Typeable[Right[A, B]]

// Collection types
implicit def genTraversableTypeable[CC[X] <: GenTraversable[X], T: Typeable]: Typeable[CC[T]]
implicit def genMapTypeable[M[X, Y], T: Typeable, U: Typeable]: Typeable[M[T, U]]

Usage Examples:

import shapeless._

val containers: List[Any] = List(
  Some(42),
  None, 
  Left("error"),
  Right(100),
  List(1, 2, 3),
  Map("a" -> 1, "b" -> 2)
)

// Cast to specific container types
val optInts = containers.flatMap(_.cast[Option[Int]])        // List(Some(42), None)
val eitherInts = containers.flatMap(_.cast[Either[String, Int]]) // List(Left("error"), Right(100))
val intLists = containers.flatMap(_.cast[List[Int]])         // List(List(1, 2, 3))
val stringIntMaps = containers.flatMap(_.cast[Map[String, Int]]) // List(Map("a" -> 1, "b" -> 2))

// Process containers safely
def processOption[T](value: Any): Option[String] = {
  value.cast[Option[T]] match {
    case Some(Some(t)) => Some(s"Some($t)")
    case Some(None) => Some("None")
    case None => None
  }
}

val optionResults = containers.flatMap(processOption[Int])
// List("Some(42)", "None")

HList Types

// HNil and HList cons
implicit val hnilTypeable: Typeable[HNil]
implicit def hlistTypeable[H: Typeable, T <: HList : Typeable]: Typeable[H :: T]

Usage Examples:

import shapeless._

val hlists: List[Any] = List(
  HNil,
  42 :: HNil,
  "hello" :: true :: HNil,
  1 :: "test" :: false :: 3.14 :: HNil
)

// Cast to specific HList types
val emptyHLists = hlists.flatMap(_.cast[HNil])                    // List(HNil)
val intHLists = hlists.flatMap(_.cast[Int :: HNil])              // List(42 :: HNil)
val stringBoolHLists = hlists.flatMap(_.cast[String :: Boolean :: HNil]) // List("hello" :: true :: HNil)

// Process HLists with known structure
def processStringBoolHList(value: Any): Option[String] = {
  value.cast[String :: Boolean :: HNil].map { hlist =>
    val str = hlist.head
    val bool = hlist.tail.head  
    s"String: $str, Boolean: $bool"
  }
}

val hlistResults = hlists.flatMap(processStringBoolHList)
// List("String: hello, Boolean: true")

Default Instance

/**
 * Default Typeable instance using ClassTag for reflection-based casting
 */
implicit def dfltTypeable[U](implicit mU: ClassTag[U]): Typeable[U] = 
  new Typeable[U] {
    def cast(t: Any): Option[U] = 
      if (mU.runtimeClass.isInstance(t)) Some(t.asInstanceOf[U]) else None
  }

This provides Typeable instances for any type that has a ClassTag, covering most user-defined types.

Usage Examples:

import shapeless._

case class Person(name: String, age: Int)
case class Product(name: String, price: Double)

val objects: List[Any] = List(
  Person("Alice", 30),
  Product("Widget", 19.99), 
  "not a case class",
  42
)

// Cast to case class types (uses default Typeable via ClassTag)
val people = objects.flatMap(_.cast[Person])     // List(Person("Alice", 30))
val products = objects.flatMap(_.cast[Product])  // List(Product("Widget", 19.99))

// Type-safe extraction and processing
def processPerson(value: Any): Option[String] = {
  value.cast[Person].map(p => s"${p.name} is ${p.age} years old")
}

def processProduct(value: Any): Option[String] = {
  value.cast[Product].map(p => s"${p.name} costs $${p.price}")
}

val descriptions = objects.flatMap(obj => 
  processPerson(obj).orElse(processProduct(obj))
)
// List("Alice is 30 years old", "Widget costs $19.99")

Advanced Usage Patterns

Type-safe Heterogeneous Collections

import shapeless._

// Type-safe heterogeneous storage
class TypedMap {
  private var storage = Map[String, Any]()
  
  def put[T: Typeable](key: String, value: T): Unit = {
    storage = storage + (key -> value)
  }
  
  def get[T: Typeable](key: String): Option[T] = {
    storage.get(key).flatMap(_.cast[T])
  }
  
  def getAll[T: Typeable]: List[T] = {
    storage.values.flatMap(_.cast[T]).toList
  }
}

val map = new TypedMap
map.put("name", "Alice")
map.put("age", 30)  
map.put("active", true)
map.put("score", 95.5)

val name: Option[String] = map.get[String]("name")     // Some("Alice")
val age: Option[Int] = map.get[Int]("age")             // Some(30)
val wrong: Option[String] = map.get[String]("age")     // None

val allStrings = map.getAll[String]  // List("Alice")
val allNumbers = map.getAll[Double]  // List(95.5)

Runtime Type Validation

import shapeless._

// Validate JSON-like structures
def validateStructure(data: Any): Either[String, (String, Int, Boolean)] = {
  data.cast[Map[String, Any]] match {
    case Some(map) =>
      for {
        name <- map.get("name").flatMap(_.cast[String])
                  .toRight("Missing or invalid 'name' field")
        age <- map.get("age").flatMap(_.cast[Int])
                 .toRight("Missing or invalid 'age' field")  
        active <- map.get("active").flatMap(_.cast[Boolean])
                    .toRight("Missing or invalid 'active' field")
      } yield (name, age, active)
    case None => Left("Expected Map structure")
  }
}

val validData = Map("name" -> "Bob", "age" -> 25, "active" -> true)
val invalidData = Map("name" -> "Carol", "age" -> "twenty-five", "active" -> true)

val result1 = validateStructure(validData)    // Right(("Bob", 25, true))
val result2 = validateStructure(invalidData)  // Left("Missing or invalid 'age' field")

Safe Deserialization

import shapeless._

// Type-safe deserialization helper
trait Deserializer[T] {
  def deserialize(data: Any): Option[T]
}

implicit def typeableDeserializer[T: Typeable]: Deserializer[T] = 
  new Deserializer[T] {
    def deserialize(data: Any): Option[T] = data.cast[T]
  }

def safeDeserialize[T: Deserializer](data: Any): Option[T] = 
  implicitly[Deserializer[T]].deserialize(data)

// Usage with various types
val rawData: List[Any] = List(42, "hello", true, List(1, 2, 3))

val deserializedInt = rawData.flatMap(safeDeserialize[Int])        // List(42)
val deserializedString = rawData.flatMap(safeDeserialize[String])  // List("hello") 
val deserializedList = rawData.flatMap(safeDeserialize[List[Int]]) // List(List(1, 2, 3))

The Typeable type class bridges the gap between compile-time and runtime type safety, enabling dynamic type checking while preserving the benefits of static typing. It's particularly useful for deserialization, dynamic dispatch, and working with heterogeneous data structures.

Install with Tessl CLI

npx tessl i tessl/maven-com-chuusai--shapeless-2-11

docs

conversions.md

generic.md

hlist.md

hmap.md

index.md

lift.md

nat.md

poly.md

records.md

sized.md

sybclass.md

typeable.md

typeoperators.md

tile.json