0
# Runtime Type Safety
1
2
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.
3
4
## Core Type Class
5
6
### Typeable Definition
7
8
```scala { .api }
9
/**
10
* Supports runtime type-safe casting for type U
11
*/
12
trait Typeable[U] {
13
/**
14
* Attempt to cast Any value to type U
15
* @param t value to cast
16
* @return Some(u) if cast succeeds, None if it fails
17
*/
18
def cast(t: Any): Option[U]
19
}
20
```
21
22
## Enhanced Casting Operations
23
24
### Cast Enhancement
25
26
```scala { .api }
27
/**
28
* Provides type-safe casting operations for any value
29
*/
30
class Cast(t: Any) {
31
/**
32
* Attempt to cast to type U
33
*/
34
def cast[U](implicit castU: Typeable[U]): Option[U] = castU.cast(t)
35
}
36
37
/**
38
* Implicit conversion to enable .cast syntax on any value
39
*/
40
implicit def anyCast(t: Any): Cast = new Cast(t)
41
```
42
43
**Usage Examples:**
44
45
```scala
46
import shapeless._
47
48
val anyValue: Any = 42
49
val stringValue: Any = "hello"
50
val listValue: Any = List(1, 2, 3)
51
52
// Safe casting with Option results
53
val asInt: Option[Int] = anyValue.cast[Int] // Some(42)
54
val asString: Option[String] = anyValue.cast[String] // None
55
val asList: Option[List[Int]] = listValue.cast[List[Int]] // Some(List(1, 2, 3))
56
57
// Use in pattern matching
58
anyValue.cast[Int] match {
59
case Some(i) => println(s"Got integer: $i")
60
case None => println("Not an integer")
61
}
62
63
// Chain operations safely
64
val result = for {
65
str <- stringValue.cast[String]
66
if str.length > 3
67
} yield str.toUpperCase
68
// result: Option[String] = Some("HELLO")
69
```
70
71
## Provided Instances
72
73
### Primitive Types
74
75
Shapeless provides `Typeable` instances for all primitive types:
76
77
```scala { .api }
78
// Numeric primitives
79
implicit val byteTypeable: Typeable[Byte]
80
implicit val shortTypeable: Typeable[Short]
81
implicit val charTypeable: Typeable[Char]
82
implicit val intTypeable: Typeable[Int]
83
implicit val longTypeable: Typeable[Long]
84
implicit val floatTypeable: Typeable[Float]
85
implicit val doubleTypeable: Typeable[Double]
86
implicit val booleanTypeable: Typeable[Boolean]
87
implicit val unitTypeable: Typeable[Unit]
88
```
89
90
**Usage Examples:**
91
92
```scala
93
import shapeless._
94
95
val mixedValues: List[Any] = List(42, 3.14, true, "hello", 'c')
96
97
// Filter by type
98
val integers = mixedValues.flatMap(_.cast[Int]) // List(42)
99
val doubles = mixedValues.flatMap(_.cast[Double]) // List(3.14)
100
val booleans = mixedValues.flatMap(_.cast[Boolean]) // List(true)
101
val chars = mixedValues.flatMap(_.cast[Char]) // List('c')
102
103
// Type-safe processing
104
def processNumeric(value: Any): Option[Double] = {
105
value.cast[Int].map(_.toDouble)
106
.orElse(value.cast[Double])
107
.orElse(value.cast[Float].map(_.toDouble))
108
.orElse(value.cast[Long].map(_.toDouble))
109
}
110
111
val numericResults = mixedValues.flatMap(processNumeric)
112
// List(42.0, 3.14)
113
```
114
115
### Hierarchy Types
116
117
```scala { .api }
118
// Type hierarchy roots
119
implicit val anyValTypeable: Typeable[AnyVal]
120
implicit val anyRefTypeable: Typeable[AnyRef]
121
```
122
123
**Usage Examples:**
124
125
```scala
126
import shapeless._
127
128
val values: List[Any] = List(42, "hello", true, List(1, 2), 3.14)
129
130
// Separate value types from reference types
131
val valueTypes = values.flatMap(_.cast[AnyVal]) // List(42, true, 3.14)
132
val referenceTypes = values.flatMap(_.cast[AnyRef]) // List("hello", List(1, 2))
133
134
// Check type categories
135
def categorizeValue(value: Any): String = {
136
if (value.cast[AnyVal].isDefined) "Value type"
137
else if (value.cast[AnyRef].isDefined) "Reference type"
138
else "Unknown type"
139
}
140
141
val categories = values.map(categorizeValue)
142
// List("Value type", "Reference type", "Value type", "Reference type", "Value type")
143
```
144
145
### Generic Container Types
146
147
```scala { .api }
148
// Option types
149
implicit def optionTypeable[T: Typeable]: Typeable[Option[T]]
150
151
// Either types
152
implicit def eitherTypeable[A: Typeable, B: Typeable]: Typeable[Either[A, B]]
153
implicit def leftTypeable[A: Typeable, B: Typeable]: Typeable[Left[A, B]]
154
implicit def rightTypeable[A: Typeable, B: Typeable]: Typeable[Right[A, B]]
155
156
// Collection types
157
implicit def genTraversableTypeable[CC[X] <: GenTraversable[X], T: Typeable]: Typeable[CC[T]]
158
implicit def genMapTypeable[M[X, Y], T: Typeable, U: Typeable]: Typeable[M[T, U]]
159
```
160
161
**Usage Examples:**
162
163
```scala
164
import shapeless._
165
166
val containers: List[Any] = List(
167
Some(42),
168
None,
169
Left("error"),
170
Right(100),
171
List(1, 2, 3),
172
Map("a" -> 1, "b" -> 2)
173
)
174
175
// Cast to specific container types
176
val optInts = containers.flatMap(_.cast[Option[Int]]) // List(Some(42), None)
177
val eitherInts = containers.flatMap(_.cast[Either[String, Int]]) // List(Left("error"), Right(100))
178
val intLists = containers.flatMap(_.cast[List[Int]]) // List(List(1, 2, 3))
179
val stringIntMaps = containers.flatMap(_.cast[Map[String, Int]]) // List(Map("a" -> 1, "b" -> 2))
180
181
// Process containers safely
182
def processOption[T](value: Any): Option[String] = {
183
value.cast[Option[T]] match {
184
case Some(Some(t)) => Some(s"Some($t)")
185
case Some(None) => Some("None")
186
case None => None
187
}
188
}
189
190
val optionResults = containers.flatMap(processOption[Int])
191
// List("Some(42)", "None")
192
```
193
194
### HList Types
195
196
```scala { .api }
197
// HNil and HList cons
198
implicit val hnilTypeable: Typeable[HNil]
199
implicit def hlistTypeable[H: Typeable, T <: HList : Typeable]: Typeable[H :: T]
200
```
201
202
**Usage Examples:**
203
204
```scala
205
import shapeless._
206
207
val hlists: List[Any] = List(
208
HNil,
209
42 :: HNil,
210
"hello" :: true :: HNil,
211
1 :: "test" :: false :: 3.14 :: HNil
212
)
213
214
// Cast to specific HList types
215
val emptyHLists = hlists.flatMap(_.cast[HNil]) // List(HNil)
216
val intHLists = hlists.flatMap(_.cast[Int :: HNil]) // List(42 :: HNil)
217
val stringBoolHLists = hlists.flatMap(_.cast[String :: Boolean :: HNil]) // List("hello" :: true :: HNil)
218
219
// Process HLists with known structure
220
def processStringBoolHList(value: Any): Option[String] = {
221
value.cast[String :: Boolean :: HNil].map { hlist =>
222
val str = hlist.head
223
val bool = hlist.tail.head
224
s"String: $str, Boolean: $bool"
225
}
226
}
227
228
val hlistResults = hlists.flatMap(processStringBoolHList)
229
// List("String: hello, Boolean: true")
230
```
231
232
### Default Instance
233
234
```scala { .api }
235
/**
236
* Default Typeable instance using ClassTag for reflection-based casting
237
*/
238
implicit def dfltTypeable[U](implicit mU: ClassTag[U]): Typeable[U] =
239
new Typeable[U] {
240
def cast(t: Any): Option[U] =
241
if (mU.runtimeClass.isInstance(t)) Some(t.asInstanceOf[U]) else None
242
}
243
```
244
245
This provides `Typeable` instances for any type that has a `ClassTag`, covering most user-defined types.
246
247
**Usage Examples:**
248
249
```scala
250
import shapeless._
251
252
case class Person(name: String, age: Int)
253
case class Product(name: String, price: Double)
254
255
val objects: List[Any] = List(
256
Person("Alice", 30),
257
Product("Widget", 19.99),
258
"not a case class",
259
42
260
)
261
262
// Cast to case class types (uses default Typeable via ClassTag)
263
val people = objects.flatMap(_.cast[Person]) // List(Person("Alice", 30))
264
val products = objects.flatMap(_.cast[Product]) // List(Product("Widget", 19.99))
265
266
// Type-safe extraction and processing
267
def processPerson(value: Any): Option[String] = {
268
value.cast[Person].map(p => s"${p.name} is ${p.age} years old")
269
}
270
271
def processProduct(value: Any): Option[String] = {
272
value.cast[Product].map(p => s"${p.name} costs $${p.price}")
273
}
274
275
val descriptions = objects.flatMap(obj =>
276
processPerson(obj).orElse(processProduct(obj))
277
)
278
// List("Alice is 30 years old", "Widget costs $19.99")
279
```
280
281
## Advanced Usage Patterns
282
283
### Type-safe Heterogeneous Collections
284
285
```scala
286
import shapeless._
287
288
// Type-safe heterogeneous storage
289
class TypedMap {
290
private var storage = Map[String, Any]()
291
292
def put[T: Typeable](key: String, value: T): Unit = {
293
storage = storage + (key -> value)
294
}
295
296
def get[T: Typeable](key: String): Option[T] = {
297
storage.get(key).flatMap(_.cast[T])
298
}
299
300
def getAll[T: Typeable]: List[T] = {
301
storage.values.flatMap(_.cast[T]).toList
302
}
303
}
304
305
val map = new TypedMap
306
map.put("name", "Alice")
307
map.put("age", 30)
308
map.put("active", true)
309
map.put("score", 95.5)
310
311
val name: Option[String] = map.get[String]("name") // Some("Alice")
312
val age: Option[Int] = map.get[Int]("age") // Some(30)
313
val wrong: Option[String] = map.get[String]("age") // None
314
315
val allStrings = map.getAll[String] // List("Alice")
316
val allNumbers = map.getAll[Double] // List(95.5)
317
```
318
319
### Runtime Type Validation
320
321
```scala
322
import shapeless._
323
324
// Validate JSON-like structures
325
def validateStructure(data: Any): Either[String, (String, Int, Boolean)] = {
326
data.cast[Map[String, Any]] match {
327
case Some(map) =>
328
for {
329
name <- map.get("name").flatMap(_.cast[String])
330
.toRight("Missing or invalid 'name' field")
331
age <- map.get("age").flatMap(_.cast[Int])
332
.toRight("Missing or invalid 'age' field")
333
active <- map.get("active").flatMap(_.cast[Boolean])
334
.toRight("Missing or invalid 'active' field")
335
} yield (name, age, active)
336
case None => Left("Expected Map structure")
337
}
338
}
339
340
val validData = Map("name" -> "Bob", "age" -> 25, "active" -> true)
341
val invalidData = Map("name" -> "Carol", "age" -> "twenty-five", "active" -> true)
342
343
val result1 = validateStructure(validData) // Right(("Bob", 25, true))
344
val result2 = validateStructure(invalidData) // Left("Missing or invalid 'age' field")
345
```
346
347
### Safe Deserialization
348
349
```scala
350
import shapeless._
351
352
// Type-safe deserialization helper
353
trait Deserializer[T] {
354
def deserialize(data: Any): Option[T]
355
}
356
357
implicit def typeableDeserializer[T: Typeable]: Deserializer[T] =
358
new Deserializer[T] {
359
def deserialize(data: Any): Option[T] = data.cast[T]
360
}
361
362
def safeDeserialize[T: Deserializer](data: Any): Option[T] =
363
implicitly[Deserializer[T]].deserialize(data)
364
365
// Usage with various types
366
val rawData: List[Any] = List(42, "hello", true, List(1, 2, 3))
367
368
val deserializedInt = rawData.flatMap(safeDeserialize[Int]) // List(42)
369
val deserializedString = rawData.flatMap(safeDeserialize[String]) // List("hello")
370
val deserializedList = rawData.flatMap(safeDeserialize[List[Int]]) // List(List(1, 2, 3))
371
```
372
373
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.