0
# Advanced Features and Utilities
1
2
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.
3
4
## Capabilities
5
6
### Zipper - Functional Cursors
7
8
Functional data structure for traversing and modifying HLists with positional context.
9
10
```scala { .api }
11
// Generic Zipper for any type with a representation via Generic
12
case class Zipper[C, L <: HList, R <: HList, P](prefix: L, suffix: R, parent: P) {
13
// Move cursor operations with type class constraints
14
def right(implicit right: Right[Self]): right.Out
15
def left(implicit left: Left[Self]): left.Out
16
def first(implicit first: First[Self]): first.Out
17
def last(implicit last: Last[Self]): last.Out
18
19
// Move by n positions
20
def rightBy[N <: Nat](implicit rightBy: RightBy[Self, N]): rightBy.Out
21
def leftBy[N <: Nat](implicit leftBy: LeftBy[Self, N]): leftBy.Out
22
23
// Modification operations
24
def put[A](a: A)(implicit put: Put[Self, A]): put.Out
25
def insert[A](a: A)(implicit insert: Insert[Self, A]): insert.Out
26
def delete(implicit delete: Delete[Self]): delete.Out
27
28
// Navigation in nested structures
29
def down(implicit down: Down[Self]): down.Out
30
def up(implicit up: Up[Self]): up.Out
31
def root(implicit root: Root[Self]): root.Out
32
}
33
34
// Zipper creation
35
trait HListZipper[L <: HList] {
36
type Out
37
def apply(l: L): Out
38
}
39
40
object Zipper {
41
// Create zipper focused on first element
42
def apply[L <: HList](l: L)(implicit zipper: HListZipper[L]): zipper.Out
43
}
44
```
45
46
### Zipper Operations
47
48
Type classes for zipper navigation and modification.
49
50
```scala { .api }
51
// Move cursor to the right (towards tail)
52
trait Right[Z] {
53
type Out
54
def apply(z: Z): Option[Out]
55
}
56
57
// Move cursor to the left (towards head)
58
trait Left[Z] {
59
type Out
60
def apply(z: Z): Option[Out]
61
}
62
63
// Move to first element
64
trait First[Z] {
65
type Out
66
def apply(z: Z): Out
67
}
68
69
// Move to last element
70
trait Last[Z] {
71
type Out
72
def apply(z: Z): Out
73
}
74
75
// Get element at cursor
76
trait Get[Z] {
77
type Out
78
def apply(z: Z): Out
79
}
80
81
// Replace element at cursor with A
82
trait Put[Z, A] {
83
type Out
84
def apply(z: Z, a: A): Out
85
}
86
87
// Insert element A at cursor
88
trait Insert[Z, A] {
89
type Out
90
def apply(z: Z, a: A): Out
91
}
92
93
// Delete element at cursor
94
trait Delete[Z] {
95
type Out
96
def apply(z: Z): Option[Out]
97
}
98
```
99
100
### Tuple Operations
101
102
Enhanced operations for working with Scala tuples through HList conversion.
103
104
```scala { .api }
105
// Witnesses tuple P can be decomposed
106
trait IsComposite[P] {
107
type H
108
type T
109
def head(p: P): H
110
def tail(p: P): T
111
}
112
113
// All but last element of tuple
114
trait Init[T] {
115
type Out
116
def apply(t: T): Out
117
}
118
119
// Last element of tuple
120
trait Last[T] {
121
type Out
122
def apply(t: T): Out
123
}
124
125
// Prepend element to tuple
126
trait Prepend[P, T] {
127
type Out
128
def apply(p: P, t: T): Out
129
}
130
131
// Reverse first tuple and prepend to second
132
trait ReversePrepend[P, Q] {
133
type Out
134
def apply(p: P, q: Q): Out
135
}
136
```
137
138
### Compile-Time Testing Utilities
139
140
Tools for compile-time verification and type checking.
141
142
```scala { .api }
143
// Assert expression has type T
144
def typed[T](t: => T): Unit = {}
145
146
// Assert two expressions have same type T
147
def sameTyped[T](t1: => T)(t2: => T): Unit = {}
148
149
// Display type T at compile time (compiler output)
150
def showType[T]: Unit = {}
151
152
// Display type bounds for T
153
def showBounds[T]: Unit = {}
154
155
// Assert code in string t fails to type check
156
def typeError(t: String): Unit = macro TestMacros.typeError
157
158
// Assert code in string t fails to compile
159
def compileError(t: String): Unit = macro TestMacros.compileError
160
161
// Deprecated alias for typeError
162
def illTyped(t: String): Unit = macro TestMacros.illTyped
163
164
// Usage:
165
typed[String]("hello") // Compiles
166
typed[Int]("hello") // Compile error
167
sameTyped(1)(2) // Compiles (both Int)
168
sameTyped("a")(1) // Compile error
169
typeError("val x: String = 123") // Succeeds (code doesn't type check)
170
```
171
172
### Runtime Type Information (Typeable)
173
174
Enhanced runtime type information beyond Scala's built-in TypeTag.
175
176
```scala { .api }
177
// Enhanced runtime type information
178
trait Typeable[T] {
179
// Safe casting with runtime type checking
180
def cast(any: Any): Option[T]
181
182
// Describe the type at runtime
183
def describe: String
184
185
// Type equality checking at runtime
186
def =:=[U](other: Typeable[U]): Boolean
187
}
188
189
object Typeable {
190
// Get Typeable instance
191
def apply[T](implicit typeable: Typeable[T]): Typeable[T] = typeable
192
193
// Built-in instances for primitive types
194
implicit val intTypeable: Typeable[Int]
195
implicit val stringTypeable: Typeable[String]
196
implicit val booleanTypeable: Typeable[Boolean]
197
198
// Generic derivation for case classes and sealed traits
199
implicit def genericTypeable[T](implicit
200
gen: Generic[T],
201
reprTypeable: Lazy[Typeable[gen.Repr]]
202
): Typeable[T]
203
}
204
```
205
206
### Annotation Support
207
208
Compile-time annotation access and processing.
209
210
```scala { .api }
211
// Extract annotation A from type T
212
trait Annotation[T, A] {
213
def apply(): A
214
}
215
216
// Extract all annotations A from type T
217
trait Annotations[T, A] {
218
def apply(): List[A]
219
}
220
221
object Annotation {
222
// Get annotation instance
223
def apply[T, A](implicit ann: Annotation[T, A]): A = ann()
224
}
225
226
// Usage with custom annotations:
227
class MyAnnotation(value: String) extends scala.annotation.StaticAnnotation
228
229
@MyAnnotation("example")
230
case class AnnotatedClass(field: String)
231
232
val annotation = Annotation[AnnotatedClass, MyAnnotation]
233
// Returns MyAnnotation("example")
234
```
235
236
### Default Value Derivation
237
238
Automatic derivation of default values for types.
239
240
```scala { .api }
241
// Provides default values for types T
242
trait Default[T] {
243
def apply(): T
244
}
245
246
object Default {
247
// Get default instance
248
def apply[T](implicit default: Default[T]): T = default()
249
250
// Built-in defaults
251
implicit val intDefault: Default[Int] = () => 0
252
implicit val stringDefault: Default[String] = () => ""
253
implicit val booleanDefault: Default[Boolean] = () => false
254
255
// Option defaults to None
256
implicit def optionDefault[T]: Default[Option[T]] = () => None
257
258
// List defaults to empty
259
implicit def listDefault[T]: Default[List[T]] = () => Nil
260
261
// Automatic derivation for case classes
262
implicit def genericDefault[A, L <: HList](implicit
263
gen: Generic.Aux[A, L],
264
defaults: Default[L]
265
): Default[A] = () => gen.from(defaults())
266
}
267
```
268
269
### Alternative Resolution (OrElse)
270
271
Alternative implicit resolution with fallback behavior.
272
273
```scala { .api }
274
// Try A, fallback to B if A not available
275
trait OrElse[A, B] {
276
type Out
277
def apply(): Out
278
}
279
280
object OrElse {
281
// Prefer primary if available
282
implicit def primary[A, B](implicit a: A): OrElse[A, B] =
283
new OrElse[A, B] {
284
type Out = A
285
def apply() = a
286
}
287
288
// Fallback to secondary
289
implicit def secondary[A, B](implicit b: B): OrElse[A, B] =
290
new OrElse[A, B] {
291
type Out = B
292
def apply() = b
293
}
294
}
295
296
// Usage for providing default implementations
297
trait Show[T] { def show(t: T): String }
298
trait PrettyShow[T] { def prettyShow(t: T): String }
299
300
def display[T](t: T)(implicit showable: OrElse[PrettyShow[T], Show[T]]): String = {
301
showable() match {
302
case pretty: PrettyShow[T] => pretty.prettyShow(t)
303
case show: Show[T] => show.show(t)
304
}
305
}
306
```
307
308
### Refutation - Negative Reasoning
309
310
Witnesses the absence of implicit instances for negative reasoning about types.
311
312
```scala { .api }
313
// Witnesses the absence of implicit instances of type T
314
trait Refute[T]
315
316
object Refute {
317
// Evidence that T is not available as implicit
318
implicit def refute[T](implicit dummy: Refute.Dummy): Refute[T] =
319
new Refute[T] {}
320
321
// Fails if T is available as implicit
322
implicit def ambiguous1[T](implicit t: T): Refute[T] = ???
323
implicit def ambiguous2[T](implicit t: T): Refute[T] = ???
324
325
trait Dummy
326
implicit val dummy: Dummy = new Dummy {}
327
}
328
329
// Usage:
330
def requireNoShow[T](implicit refute: Refute[Show[T]]): String =
331
"No Show instance available for T"
332
```
333
334
### Unwrapped Types
335
336
Automatic unwrapping of newtype/tagged wrapper types.
337
338
```scala { .api }
339
// Unwrapper from wrapper W to underlying U
340
trait Unwrapped[W] {
341
type U
342
def unwrap(w: W): U
343
}
344
345
object Unwrapped {
346
// Type alias with fixed underlying type
347
type Aux[W, U0] = Unwrapped[W] { type U = U0 }
348
349
// Unwrap tagged types
350
implicit def taggedUnwrapped[T, Tag]: Unwrapped.Aux[T @@ Tag, T] =
351
new Unwrapped[T @@ Tag] {
352
type U = T
353
def unwrap(tagged: T @@ Tag): T = tagged
354
}
355
}
356
```
357
358
### Standard Library Extensions
359
360
Extensions and integrations with Scala's standard library.
361
362
```scala { .api }
363
// Either extensions
364
implicit class EitherOps[A, B](either: Either[A, B]) {
365
def toHList: A :+: B :+: CNil = either match {
366
case Left(a) => Inl(a)
367
case Right(b) => Inr(Inl(b))
368
}
369
}
370
371
// Function extensions
372
implicit class FunctionOps[A, B](f: A => B) {
373
def toPolyTrain: Poly1 = new Poly1 {
374
implicit def cse = at[A](f)
375
}
376
}
377
378
// Map extensions
379
implicit class MapOps[K, V](map: Map[K, V]) {
380
def toRecord: HList = ??? // Convert to record representation
381
}
382
383
// Traversable extensions
384
implicit class TraversableOps[A](trav: Traversable[A]) {
385
def toHList: HList = ??? // Convert to HList if homogeneous
386
}
387
```
388
389
## Usage Examples
390
391
### Zipper Navigation
392
393
```scala
394
import shapeless._, zipper._
395
396
val data = "first" :: 2 :: true :: "last" :: HNil
397
398
// Create zipper focused on first element
399
val zipper = data.toZipper
400
// Zipper focused on "first"
401
402
// Navigate
403
val moved = zipper.right.right // Now focused on true
404
val atEnd = moved.right // Now focused on "last"
405
val backToFirst = atEnd.first // Back to "first"
406
407
// Modify at cursor
408
val modified = moved.put(false) // Replace true with false
409
val inserted = moved.insert("new") // Insert "new" before true
410
val deleted = moved.delete // Remove true
411
412
// Convert back to HList
413
val result = modified.toHList
414
// "first" :: 2 :: false :: "last" :: HNil
415
```
416
417
### Runtime Type Checking
418
419
```scala
420
import shapeless._, typeable._
421
422
def safeCast[T: Typeable](any: Any): Option[T] =
423
Typeable[T].cast(any)
424
425
// Safe casting with runtime checks
426
val value: Any = "hello world"
427
428
val asString = safeCast[String](value) // Some("hello world")
429
val asInt = safeCast[Int](value) // None
430
val asList = safeCast[List[String]](value) // None
431
432
// Type descriptions
433
val stringDesc = Typeable[String].describe // "String"
434
val listDesc = Typeable[List[Int]].describe // "List[Int]"
435
val complexDesc = Typeable[(String, Option[Int])].describe // "(String, Option[Int])"
436
```
437
438
### Compile-Time Testing
439
440
```scala
441
import shapeless._, test._
442
443
// Verify types at compile time
444
typed[String]("hello") // ✓ Compiles
445
typed[Int](42) // ✓ Compiles
446
// typed[String](42) // ✗ Compile error
447
448
// Verify same types
449
sameTyped(List(1, 2, 3))(List(4, 5, 6)) // ✓ Both List[Int]
450
// sameTyped(List(1, 2, 3))("hello") // ✗ Different types
451
452
// Verify code fails to compile
453
typeError("val x: String = 123") // ✓ Code doesn't type check
454
typeError("val y: Int = 456") // ✗ Code does type check
455
456
// Debug types at compile time
457
showType[List[String]] // Prints type to compiler output
458
showBounds[List[_]] // Shows type bounds
459
```
460
461
### Default Value Generation
462
463
```scala
464
import shapeless._, Default._
465
466
case class Config(
467
host: String,
468
port: Int,
469
ssl: Boolean,
470
timeout: Option[Int],
471
retries: List[Int]
472
)
473
474
// Automatic default derivation
475
implicit val hostDefault: Default[String] = () => "localhost"
476
implicit val portDefault: Default[Int] = () => 8080
477
implicit val sslDefault: Default[Boolean] = () => false
478
479
val defaultConfig = Default[Config].apply()
480
// Config("localhost", 8080, false, None, List())
481
482
// Manual defaults
483
case class Person(name: String, age: Int = 0, active: Boolean = true)
484
485
val defaultPerson = Default[Person].apply()
486
// Uses field defaults where available
487
```
488
489
### Annotation Processing
490
491
```scala
492
import shapeless._, annotation._
493
494
// Custom annotations
495
class ApiDoc(description: String) extends scala.annotation.StaticAnnotation
496
class Deprecated(since: String) extends scala.annotation.StaticAnnotation
497
498
@ApiDoc("User information")
499
case class User(
500
@ApiDoc("Full name") name: String,
501
@ApiDoc("Age in years") @Deprecated("2.0") age: Int,
502
@ApiDoc("Account status") active: Boolean
503
)
504
505
// Extract annotations
506
val classDoc = Annotation[User, ApiDoc]
507
// ApiDoc("User information")
508
509
val fieldDocs = Annotations[User, ApiDoc]
510
// List(ApiDoc("Full name"), ApiDoc("Age in years"), ApiDoc("Account status"))
511
512
val deprecations = Annotations[User, Deprecated]
513
// List(Deprecated("2.0"))
514
```
515
516
### Alternative Resolution
517
518
```scala
519
import shapeless._, OrElse._
520
521
// Preference system for formatting
522
trait JsonFormat[T] { def toJson(t: T): String }
523
trait SimpleFormat[T] { def format(t: T): String }
524
525
implicit val intJson: JsonFormat[Int] = i => s"""{"value": $i}"""
526
implicit val stringSimple: SimpleFormat[String] = s => s""""$s""""
527
528
def formatValue[T](t: T)(implicit
529
formatter: OrElse[JsonFormat[T], SimpleFormat[T]]
530
): String = {
531
formatter() match {
532
case json: JsonFormat[T] => json.toJson(t)
533
case simple: SimpleFormat[T] => simple.format(t)
534
}
535
}
536
537
val intFormatted = formatValue(42) // Uses JsonFormat: {"value": 42}
538
val stringFormatted = formatValue("hi") // Uses SimpleFormat: "hi"
539
```
540
541
### Unwrapping Tagged Types
542
543
```scala
544
import shapeless._, tag._, Unwrapped._
545
546
// Tagged types for domain modeling
547
trait UserId
548
trait Email
549
550
type UserIdValue = String @@ UserId
551
type EmailValue = String @@ Email
552
553
val userId: UserIdValue = tag[UserId]("user123")
554
val email: EmailValue = tag[Email]("test@example.com")
555
556
// Automatic unwrapping
557
def processId[T](wrapped: T)(implicit
558
unwrapped: Unwrapped[T]
559
): unwrapped.U = unwrapped.unwrap(wrapped)
560
561
val rawUserId: String = processId(userId) // "user123"
562
val rawEmail: String = processId(email) // "test@example.com"
563
564
// Type-safe unwrapping preserves information
565
def logValue[W](wrapped: W)(implicit
566
unwrapped: Unwrapped.Aux[W, String]
567
): Unit = println(s"Value: ${unwrapped.unwrap(wrapped)}")
568
569
logValue(userId) // "Value: user123"
570
logValue(email) // "Value: test@example.com"
571
```