0
# Generic Programming Utilities
1
2
Shapeless provides advanced utilities for generic programming including lenses for functional references, zippers for navigation and updates, isomorphisms for bidirectional conversions, and various other tools that enable powerful abstractions over data structures.
3
4
## Lenses
5
6
Lenses provide functional references to parts of immutable data structures, enabling safe updates without mutation.
7
8
### Core Lens Type
9
10
```scala { .api }
11
/**
12
* Functional reference to a field F within container C
13
*/
14
trait Lens[C, F] {
15
def get(c: C): F
16
def set(c: C)(f: F): C
17
def modify(c: C)(f: F => F): C = set(c)(f(get(c)))
18
}
19
```
20
21
### Lens Operations
22
23
```scala { .api }
24
/**
25
* Compose with another lens
26
*/
27
def compose[D](g: Lens[D, C]): Lens[D, F]
28
29
/**
30
* Index into HList field (when F is an HList)
31
*/
32
def >>[L <: HList, N <: Nat](n: N)
33
(implicit iso: Iso[F, L], lens: HListNthLens[L, N]): Lens[C, lens.Elem]
34
```
35
36
### Lens Factory Methods
37
38
```scala { .api }
39
object Lens {
40
/**
41
* Identity lens
42
*/
43
def apply[C] = id[C]
44
def id[C]: Lens[C, C]
45
46
/**
47
* Set membership lens
48
*/
49
def setLens[E](e: E): Lens[Set[E], Boolean]
50
51
/**
52
* Map value lens
53
*/
54
def mapLens[K, V](k: K): Lens[Map[K, V], Option[V]]
55
56
/**
57
* HList position lens
58
*/
59
def hlistNthLens[L <: HList, N <: Nat]: HListNthLens[L, N]
60
}
61
```
62
63
**Usage Examples:**
64
65
```scala
66
import shapeless._
67
import shapeless.lens._
68
69
case class Address(street: String, city: String, zip: String)
70
case class Person(name: String, age: Int, address: Address)
71
72
val person = Person("Alice", 30, Address("123 Main St", "Boston", "02101"))
73
74
// Create lenses (typically done with macro support in real usage)
75
val nameLens = Lens[Person, String](_.name, p => n => p.copy(name = n))
76
val ageLens = Lens[Person, Int](_.age, p => a => p.copy(age = a))
77
val addressLens = Lens[Person, Address](_.address, p => a => p.copy(address = a))
78
79
// Use lenses
80
val name = nameLens.get(person) // "Alice"
81
val olderPerson = ageLens.set(person)(31) // Person with age = 31
82
val happyPerson = nameLens.modify(person)(_ + " Smith") // "Alice Smith"
83
84
// Compose lenses for nested access
85
val streetLens = Lens[Address, String](_.street, a => s => a.copy(street = s))
86
val personStreetLens = addressLens.compose(streetLens)
87
88
val street = personStreetLens.get(person) // "123 Main St"
89
val movedPerson = personStreetLens.set(person)("456 Oak Ave")
90
```
91
92
### HList Lenses
93
94
```scala { .api }
95
/**
96
* Lens for accessing nth element of HList
97
*/
98
trait HListNthLens[L <: HList, N <: Nat] {
99
type Elem
100
def get(l: L): Elem
101
def set(l: L)(e: Elem): L
102
def toLens: Lens[L, Elem]
103
}
104
```
105
106
**Usage Examples:**
107
108
```scala
109
import shapeless._
110
111
val hlist = "hello" :: 42 :: true :: 3.14 :: HNil
112
113
// Access by index
114
val lens0 = Lens.hlistNthLens[String :: Int :: Boolean :: Double :: HNil, _0]
115
val lens1 = Lens.hlistNthLens[String :: Int :: Boolean :: Double :: HNil, _1]
116
117
val first: String = lens0.get(hlist) // "hello"
118
val second: Int = lens1.get(hlist) // 42
119
120
val updated = lens1.set(hlist)(99) // "hello" :: 99 :: true :: 3.14 :: HNil
121
```
122
123
### Product Lenses
124
125
```scala { .api }
126
/**
127
* Combine multiple lenses into a product lens
128
*/
129
trait ProductLens[C, P <: Product] extends Lens[C, P] {
130
def ~[T, L <: HList, LT <: HList, Q <: Product](other: Lens[C, T]): ProductLens[C, Q]
131
}
132
```
133
134
**Usage Examples:**
135
136
```scala
137
import shapeless._
138
139
case class User(id: Int, name: String, email: String, active: Boolean)
140
val user = User(1, "Bob", "bob@example.com", true)
141
142
// Create individual lenses
143
val idLens = Lens[User, Int](_.id, u => i => u.copy(id = i))
144
val nameLens = Lens[User, String](_.name, u => n => u.copy(name = n))
145
146
// Combine into product lens
147
val idNameLens = idLens ~ nameLens // ProductLens[User, (Int, String)]
148
149
val (id, name) = idNameLens.get(user) // (1, "Bob")
150
val updated = idNameLens.set(user)(2, "Robert") // User(2, "Robert", ...)
151
```
152
153
## Zippers
154
155
Zippers provide cursors for navigating and updating tree-like data structures.
156
157
### Core Zipper Type
158
159
```scala { .api }
160
/**
161
* Generic zipper for navigating and updating data structures
162
* C - Container type, L - Left context, R - Right context, P - Parent context
163
*/
164
case class Zipper[C, L <: HList, R <: HList, P](prefix: L, suffix: R, parent: P)
165
```
166
167
### Navigation Operations
168
169
```scala { .api }
170
// Horizontal movement
171
def right(implicit right: Right[Self]): right.Out
172
def left(implicit left: Left[Self]): left.Out
173
def first(implicit first: First[Self]): first.Out
174
def last(implicit last: Last[Self]): last.Out
175
176
// Positional movement
177
def rightBy[N <: Nat](implicit rightBy: RightBy[Self, N]): rightBy.Out
178
def leftBy[N <: Nat](implicit leftBy: LeftBy[Self, N]): leftBy.Out
179
def rightTo[T](implicit rightTo: RightTo[Self, T]): rightTo.Out
180
def leftTo[T](implicit leftTo: LeftTo[Self, T]): leftTo.Out
181
182
// Vertical movement
183
def up(implicit up: Up[Self]): up.Out
184
def down(implicit down: Down[Self]): down.Out
185
def root(implicit root: Root[Self]): root.Out
186
```
187
188
### Modification Operations
189
190
```scala { .api }
191
// Access and update
192
def get(implicit get: Get[Self]): get.Out
193
def put[E](e: E)(implicit put: Put[Self, E]): put.Out
194
def insert[E](e: E)(implicit insert: Insert[Self, E]): insert.Out
195
def delete(implicit delete: Delete[Self]): delete.Out
196
197
// Reification
198
def reify(implicit reify: Reify[Self]): reify.Out
199
```
200
201
**Usage Examples:**
202
203
```scala
204
import shapeless._
205
206
val hlist = 1 :: "hello" :: true :: 3.14 :: HNil
207
val zipper = hlist.toZipper
208
209
// Navigate and access
210
val atFirst = zipper.first
211
val current = atFirst.get // 1
212
val atSecond = atFirst.right
213
val secondValue = atSecond.get // "hello"
214
215
// Modify
216
val withNewValue = atSecond.put("world")
217
val result = withNewValue.reify // 1 :: "world" :: true :: 3.14 :: HNil
218
219
// Insert and delete
220
val withInserted = atSecond.insert("inserted")
221
val afterDeletion = withInserted.right.delete
222
val final = afterDeletion.reify
223
```
224
225
## Isomorphisms
226
227
Isomorphisms represent bidirectional conversions between types.
228
229
### Core Isomorphism Type
230
231
```scala { .api }
232
/**
233
* Bidirectional transformation between types T and U
234
*/
235
trait Iso[T, U] {
236
def to(t: T): U
237
def from(u: U): T
238
def reverse: Iso[U, T]
239
}
240
```
241
242
### Isomorphism Factory
243
244
```scala { .api }
245
object Iso extends LowPriorityIso {
246
/**
247
* Special case for single-element case classes
248
*/
249
def hlist[CC, T](apply: T => CC, unapply: CC => Option[T]): Iso[CC, T :: HNil]
250
251
/**
252
* General case class iso factory
253
*/
254
def hlist[CC, C, T <: Product, L <: HList](apply: C, unapply: CC => Option[T]): Iso[CC, L]
255
256
// Implicit isos
257
implicit def tupleHListIso[T <: Product, L <: HList]: Iso[T, L]
258
implicit def fnHListFnIso[F, L <: HList, R]: Iso[F, L => R]
259
implicit def identityIso[T]: Iso[T, T]
260
}
261
```
262
263
**Usage Examples:**
264
265
```scala
266
import shapeless._
267
268
case class Point(x: Double, y: Double)
269
270
// Create isomorphisms (typically auto-derived)
271
val pointIso: Iso[Point, Double :: Double :: HNil] =
272
Iso.hlist(Point.apply _, Point.unapply _)
273
274
val point = Point(3.0, 4.0)
275
val hlist = pointIso.to(point) // 3.0 :: 4.0 :: HNil
276
val backToPoint = pointIso.from(hlist) // Point(3.0, 4.0)
277
278
// Use with generic operations
279
val doubled = hlist.map(new (Double => Double)(_ * 2))
280
val doubledPoint = pointIso.from(doubled) // Point(6.0, 8.0)
281
```
282
283
## Type Operators
284
285
Advanced type-level programming constructs.
286
287
### Basic Type Functions
288
289
```scala { .api }
290
type Id[+T] = T
291
type Const[C] = { type λ[T] = C }
292
```
293
294
### Logic Operations
295
296
```scala { .api }
297
type ¬[T] = T => Nothing // Negation
298
type ¬¬[T] = ¬[¬[T]] // Double negation
299
type ∧[T, U] = T with U // Conjunction
300
type ∨[T, U] = ¬[¬[T] ∧ ¬[U]] // Disjunction
301
```
302
303
### Type Inequalities
304
305
```scala { .api }
306
/**
307
* Type inequality witness
308
*/
309
trait =:!=[A, B]
310
implicit def neq[A, B]: A =:!= B
311
implicit def neqAmbig1[A]: A =:!= A = ??? // Ambiguous for equality
312
implicit def neqAmbig2[A]: A =:!= A = ???
313
314
/**
315
* Subtype inequality witness
316
*/
317
trait <:!<[A, B]
318
implicit def nsub[A, B]: A <:!< B
319
implicit def nsubAmbig1[A, B >: A]: A <:!< B = ??? // Ambiguous for subtyping
320
implicit def nsubAmbig2[A, B >: A]: A <:!< B = ???
321
```
322
323
**Usage Examples:**
324
325
```scala
326
import shapeless._
327
328
// Ensure types are different
329
def processIfDifferent[A, B](a: A, b: B)(implicit ev: A =:!= B): String =
330
s"Processing different types: $a and $b"
331
332
val result1 = processIfDifferent(42, "hello") // Works
333
// val result2 = processIfDifferent(42, 24) // Error: Int =:!= Int fails
334
335
// Ensure no subtype relationship
336
def requireUnrelated[A, B](a: A, b: B)(implicit ev1: A <:!< B, ev2: B <:!< A): Unit = ()
337
338
requireUnrelated("hello", 42) // Works: String and Int unrelated
339
// requireUnrelated("hello", "world") // Error: String <:!< String fails
340
```
341
342
### Tagged Types
343
344
```scala { .api }
345
/**
346
* Tagged type T with tag U
347
*/
348
trait Tagged[U]
349
type @@[T, U] = T with Tagged[U]
350
351
class Tagger[U] {
352
def apply[T](t: T): T @@ U = t.asInstanceOf[T @@ U]
353
}
354
355
def tag[U] = new Tagger[U]
356
```
357
358
**Usage Examples:**
359
360
```scala
361
import shapeless._
362
import tag._
363
364
// Create tagged types for type safety
365
trait UserId
366
trait ProductId
367
368
val userId: Int @@ UserId = tag[UserId](12345)
369
val productId: Int @@ ProductId = tag[ProductId](67890)
370
371
def lookupUser(id: Int @@ UserId): String = s"User ${id}"
372
def lookupProduct(id: Int @@ ProductId): String = s"Product ${id}"
373
374
val user = lookupUser(userId) // Works
375
val product = lookupProduct(productId) // Works
376
// val wrong = lookupUser(productId) // Error: type mismatch
377
```
378
379
### Newtypes
380
381
```scala { .api }
382
/**
383
* Newtype wrapper with operations
384
*/
385
type Newtype[Repr, Ops] = Any @@ NewtypeTag[Repr, Ops]
386
trait NewtypeTag[Repr, Ops]
387
388
def newtype[Repr, Ops](r: Repr): Newtype[Repr, Ops]
389
implicit def newtypeOps[Repr, Ops](t: Newtype[Repr, Ops])
390
(implicit mkOps: Repr => Ops): Ops
391
```
392
393
## Advanced Generic Operations
394
395
### Type Class Derivation
396
397
```scala { .api }
398
/**
399
* Abstracts over type class derivation for product types
400
*/
401
trait TypeClass[C[_]] {
402
def product[H, T <: HList](CHead: C[H], CTail: C[T]): C[H :: T]
403
def emptyProduct: C[HNil]
404
def derive[F, G](instance: C[G], iso: Iso[F, G]): C[F]
405
}
406
```
407
408
**Usage Examples:**
409
410
```scala
411
import shapeless._
412
413
// Example: Show type class derivation
414
trait Show[T] {
415
def show(t: T): String
416
}
417
418
implicit val showInt: Show[Int] = _.toString
419
implicit val showString: Show[String] = identity
420
implicit val showBoolean: Show[Boolean] = _.toString
421
422
implicit val showHNil: Show[HNil] = _ => ""
423
implicit def showHList[H, T <: HList]
424
(implicit showH: Show[H], showT: Show[T]): Show[H :: T] =
425
hlist => s"${showH.show(hlist.head)} :: ${showT.show(hlist.tail)}"
426
427
val hlist = 42 :: "hello" :: true :: HNil
428
val shown = implicitly[Show[Int :: String :: Boolean :: HNil]].show(hlist)
429
// "42 :: hello :: true :: "
430
```
431
432
These generic programming utilities provide the foundation for building sophisticated abstractions while maintaining type safety and enabling automatic derivation of type class instances.