0
# Generic Programming and Automatic Derivation
1
2
Generic programming in Shapeless enables automatic derivation of type class instances by converting between user-defined types and generic representations. This is the core value proposition of Shapeless - eliminating boilerplate code while maintaining complete type safety through compile-time transformations.
3
4
## Capabilities
5
6
### Generic Type Class
7
8
The fundamental type class that establishes bidirectional conversion between concrete types and their generic representations.
9
10
```scala { .api }
11
trait Generic[A] {
12
// Associated type for the generic representation
13
type Repr
14
15
// Convert from concrete type to generic representation
16
def to(a: A): Repr
17
18
// Convert from generic representation back to concrete type
19
def from(repr: Repr): A
20
}
21
22
object Generic {
23
// Type alias with fixed representation type
24
type Aux[A, Repr0] = Generic[A] { type Repr = Repr0 }
25
26
// Summoner method to get Generic instance
27
def apply[A](implicit gen: Generic[A]): Generic.Aux[A, gen.Repr]
28
29
// Manually create Generic instance
30
def instance[A, R](to: A => R, from: R => A): Generic.Aux[A, R]
31
32
// Macro-generated instances for case classes and sealed traits
33
implicit def materialize[A, R]: Generic.Aux[A, R] = macro GenericMacros.materialize[A, R]
34
}
35
```
36
37
### LabelledGeneric Type Class
38
39
Enhanced version of Generic that preserves field names and constructor names as singleton types in the representation.
40
41
```scala { .api }
42
trait LabelledGeneric[A] {
43
// Associated type preserving labels as singleton types
44
type Repr
45
46
// Convert to labeled generic representation
47
def to(a: A): Repr
48
49
// Convert from labeled representation back to concrete type
50
def from(repr: Repr): A
51
}
52
53
object LabelledGeneric {
54
// Type alias with fixed representation
55
type Aux[A, Repr0] = LabelledGeneric[A] { type Repr = Repr0 }
56
57
// Summoner method
58
def apply[A](implicit lgen: LabelledGeneric[A]): LabelledGeneric.Aux[A, lgen.Repr]
59
60
// Separate materialization for products and coproducts
61
implicit def materializeProduct[A, L <: HList]: LabelledGeneric.Aux[A, L] =
62
macro LabelledGenericMacros.materializeProduct[A, L]
63
64
implicit def materializeCoproduct[A, C <: Coproduct]: LabelledGeneric.Aux[A, C] =
65
macro LabelledGenericMacros.materializeCoproduct[A, C]
66
}
67
```
68
69
### Type Class Witnesses
70
71
Type classes that provide evidence about the generic structure of types.
72
73
```scala { .api }
74
// Witnesses that T has product (case class/tuple) representation
75
trait HasProductGeneric[T] {
76
type Repr <: HList
77
}
78
79
// Witnesses that T has coproduct (sealed trait) representation
80
trait HasCoproductGeneric[T] {
81
type Repr <: Coproduct
82
}
83
84
// Witnesses that T corresponds to a tuple type
85
trait IsTuple[T] {
86
type Repr <: HList
87
}
88
89
// Get evidence for product structure
90
def productGeneric[T](implicit hpg: HasProductGeneric[T]): HasProductGeneric.Aux[T, hpg.Repr]
91
92
// Get evidence for coproduct structure
93
def coproductGeneric[T](implicit hcg: HasCoproductGeneric[T]): HasCoproductGeneric.Aux[T, hcg.Repr]
94
```
95
96
### Higher-Kinded Generic (Generic1)
97
98
Generic programming for type constructors (types with one type parameter).
99
100
```scala { .api }
101
trait Generic1[F[_], FR[_[_]]] {
102
// Convert type constructor to generic representation
103
def to[A](fa: F[A]): FR[({ type λ[B] = A })#λ][A]
104
105
// Convert from generic representation
106
def from[A](fra: FR[({ type λ[B] = A })#λ][A]): F[A]
107
}
108
109
// Example for Option
110
trait OptionGeneric extends Generic1[Option, ({ type λ[G[_]] = G[Nothing] :+: (G ~> G) :+: CNil })#λ]
111
```
112
113
### Automatic Derivation Infrastructure
114
115
Supporting type classes for automatic instance derivation.
116
117
```scala { .api }
118
// Lazy evaluation for recursive derivation
119
trait Lazy[T] {
120
val value: T
121
}
122
123
object Lazy {
124
// Create lazy instance
125
implicit def apply[T](implicit t: => T): Lazy[T] = new Lazy[T] {
126
lazy val value: T = t
127
}
128
}
129
130
// Cached implicit resolution
131
trait Cached[T] {
132
val value: T
133
}
134
135
object Cached {
136
implicit def apply[T](implicit t: T): Cached[T] = new Cached[T] {
137
val value: T = t
138
}
139
}
140
141
// Default value provision
142
trait Default[T] {
143
def apply(): T
144
}
145
146
object Default {
147
// Default for Option is None
148
implicit def optionDefault[T]: Default[Option[T]] = () => None
149
150
// Automatic derivation for case classes
151
implicit def genericDefault[A, L <: HList](implicit
152
gen: Generic.Aux[A, L],
153
defaults: Default[L]
154
): Default[A] = () => gen.from(defaults())
155
}
156
```
157
158
### Derivation Annotations
159
160
Annotations to control generic derivation behavior.
161
162
```scala { .api }
163
// Exclude field or method from generic derivation
164
class nonGeneric extends scala.annotation.StaticAnnotation
165
166
// Usage:
167
case class Person(
168
name: String,
169
age: Int,
170
@nonGeneric internal: String = "hidden"
171
)
172
// Generic representation excludes 'internal' field
173
```
174
175
## Usage Examples
176
177
### Basic Generic Derivation
178
179
```scala
180
import shapeless._
181
182
// Define case class
183
case class Person(name: String, age: Int, active: Boolean)
184
185
// Get generic instance
186
val gen = Generic[Person]
187
// gen.Repr is String :: Int :: Boolean :: HNil
188
189
val person = Person("Alice", 30, true)
190
191
// Convert to generic representation
192
val repr = gen.to(person)
193
// repr: String :: Int :: Boolean :: HNil = "Alice" :: 30 :: true :: HNil
194
195
// Convert back to case class
196
val restored = gen.from(repr)
197
// restored: Person = Person("Alice", 30, true)
198
```
199
200
### Labeled Generic with Field Names
201
202
```scala
203
import shapeless._, labelled.FieldType
204
205
case class Book(title: String, pages: Int, published: Boolean)
206
207
val lgen = LabelledGeneric[Book]
208
// lgen.Repr preserves field names as singleton types
209
210
val book = Book("1984", 328, true)
211
val labeled = lgen.to(book)
212
// Result has type: FieldType['title, String] :: FieldType['pages, Int] :: FieldType['published, Boolean] :: HNil
213
214
// Access with field names preserved
215
val title = labeled.select[FieldType['title, String]]
216
val pages = labeled.select[FieldType['pages, Int]]
217
```
218
219
### Generic Derivation for Sealed Traits
220
221
```scala
222
sealed trait Shape
223
case class Circle(radius: Double) extends Shape
224
case class Rectangle(width: Double, height: Double) extends Shape
225
case class Triangle(base: Double, height: Double) extends Shape
226
227
val shapeGen = Generic[Shape]
228
// shapeGen.Repr is Circle :+: Rectangle :+: Triangle :+: CNil
229
230
val circle: Shape = Circle(5.0)
231
val circleRepr = shapeGen.to(circle)
232
// circleRepr: Circle :+: Rectangle :+: Triangle :+: CNil = Inl(Circle(5.0))
233
234
val rect: Shape = shapeGen.from(Inr(Inl(Rectangle(10.0, 20.0))))
235
// rect: Shape = Rectangle(10.0, 20.0)
236
```
237
238
### Automatic Type Class Derivation
239
240
```scala
241
// Define type class
242
trait Show[T] {
243
def show(t: T): String
244
}
245
246
// Base instances
247
implicit val stringShow: Show[String] = identity
248
implicit val intShow: Show[Int] = _.toString
249
implicit val boolShow: Show[Boolean] = _.toString
250
251
// HList instances
252
implicit val hnilShow: Show[HNil] = _ => ""
253
254
implicit def hconsShow[H, T <: HList](implicit
255
hShow: Show[H],
256
tShow: Show[T]
257
): Show[H :: T] = {
258
case h :: t => s"${hShow.show(h)} :: ${tShow.show(t)}"
259
}
260
261
// Automatic derivation for case classes
262
implicit def genericShow[A, R](implicit
263
gen: Generic.Aux[A, R],
264
rShow: Show[R]
265
): Show[A] = a => rShow.show(gen.to(a))
266
267
// Usage - Show instance automatically derived
268
case class Employee(name: String, id: Int, active: Boolean)
269
val emp = Employee("Bob", 123, true)
270
println(emp.show) // Outputs: "Bob :: 123 :: true :: "
271
```
272
273
### Recursive Derivation with Lazy
274
275
```scala
276
// Recursive data structure
277
sealed trait Tree[A]
278
case class Leaf[A](value: A) extends Tree[A]
279
case class Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A]
280
281
// Define type class for tree processing
282
trait TreeSize[T] {
283
def size(t: T): Int
284
}
285
286
// Base instances
287
implicit val leafSize: TreeSize[Leaf[_]] = _ => 1
288
289
implicit def branchSize[A](implicit
290
treeSize: Lazy[TreeSize[Tree[A]]] // Lazy for recursive reference
291
): TreeSize[Branch[A]] = branch =>
292
1 + treeSize.value.size(branch.left) + treeSize.value.size(branch.right)
293
294
// Generic derivation for Tree
295
implicit def treeSize[A](implicit
296
gen: Generic[Tree[A]],
297
reprSize: Lazy[TreeSize[gen.Repr]]
298
): TreeSize[Tree[A]] = tree => reprSize.value.size(gen.to(tree))
299
300
// Usage
301
val tree: Tree[Int] = Branch(
302
Leaf(1),
303
Branch(Leaf(2), Leaf(3))
304
)
305
println(tree.size) // Outputs: 5 (3 leaves + 2 branches)
306
```
307
308
### Default Value Derivation
309
310
```scala
311
case class Config(
312
host: String,
313
port: Int,
314
debug: Boolean,
315
timeout: Option[Int]
316
)
317
318
// Derive default instance automatically
319
implicit val stringDefault: Default[String] = () => "localhost"
320
implicit val intDefault: Default[Int] = () => 8080
321
implicit val boolDefault: Default[Boolean] = () => false
322
323
val defaultConfig = Default[Config].apply()
324
// Config("localhost", 8080, false, None)
325
```
326
327
### Working with Tuples
328
329
```scala
330
// Tuples have automatic Generic instances
331
val tuple = ("hello", 42, true)
332
val tupleGen = Generic[(String, Int, Boolean)]
333
334
val tupleAsHList = tupleGen.to(tuple)
335
// tupleAsHList: String :: Int :: Boolean :: HNil
336
337
// Transform via HList operations
338
val modified = tupleAsHList.updatedBy[String](_.toUpperCase)
339
val backToTuple = tupleGen.from(modified)
340
// backToTuple: (String, Int, Boolean) = ("HELLO", 42, true)
341
```
342
343
### Custom Generic Instances
344
345
```scala
346
// Define custom type
347
class CustomContainer[A](val items: List[A]) {
348
def get: List[A] = items
349
}
350
351
// Manual Generic instance
352
implicit def customGeneric[A]: Generic.Aux[CustomContainer[A], List[A] :: HNil] =
353
Generic.instance(
354
to = container => container.items :: HNil,
355
from = {
356
case items :: HNil => new CustomContainer(items)
357
}
358
)
359
360
// Now CustomContainer works with generic derivation
361
val container = new CustomContainer(List(1, 2, 3))
362
val repr = Generic[CustomContainer[Int]].to(container)
363
// repr: List[Int] :: HNil = List(1, 2, 3) :: HNil
364
```