An exploration of generic (aka polytypic) programming in Scala derived from implementing scrap your boilerplate and higher rank polymorphism patterns
npx @tessl/cli install tessl/maven-com-chuusai--shapeless-2-11@1.2.00
# Shapeless
1
2
Shapeless is a type class and dependent type based generic programming library for Scala. It provides powerful abstractions for working with heterogeneous data structures and enables type-safe generic programming through compile-time type manipulation and automatic type class derivation.
3
4
## Package Information
5
6
- **Package Name**: shapeless
7
- **Package Type**: Scala library
8
- **Version**: 1.2.4
9
- **Language**: Scala
10
- **Installation**: Add to your `build.sbt`:
11
12
```scala
13
libraryDependencies += "com.chuusai" %% "shapeless" % "1.2.4"
14
```
15
16
## Core Imports
17
18
```scala { .api }
19
import shapeless._
20
```
21
22
For specific functionality, use targeted imports:
23
24
```scala { .api }
25
import shapeless.HList._
26
import shapeless.Poly._
27
import shapeless.Nat._
28
import shapeless.record._
29
import shapeless.syntax.sized._
30
```
31
32
## Basic Usage
33
34
```scala { .api }
35
import shapeless._
36
37
// Create heterogeneous lists
38
val hlist = 23 :: "foo" :: true :: HNil
39
val head: Int = hlist.head
40
val tail = hlist.tail
41
42
// Type-safe record operations
43
val book = ("author" ->> "Benjamin Pierce") :: ("title" ->> "TAPL") :: ("id" ->> 991) :: HNil
44
val author: String = book("author")
45
val updated = book.updated("price", 89.95)
46
47
// Polymorphic functions
48
object size extends Poly1 {
49
implicit def caseInt = at[Int](identity)
50
implicit def caseString = at[String](_.length)
51
implicit def caseList[T] = at[List[T]](_.length)
52
}
53
54
val sizes = (42 :: "hello" :: List(1, 2, 3) :: HNil).map(size)
55
// sizes: Int :: Int :: Int :: HNil = 42 :: 5 :: 3 :: HNil
56
57
// Heterogeneous maps with type-level relations
58
class BiMapIS[K, V]
59
implicit val intToString = new BiMapIS[Int, String]
60
implicit val stringToInt = new BiMapIS[String, Int]
61
62
val hmap = HMap[BiMapIS](23 -> "twenty-three", "age" -> 30)
63
val str: Option[String] = hmap.get(23) // Some("twenty-three")
64
val int: Option[Int] = hmap.get("age") // Some(30)
65
66
// Function lifting into Option
67
val add: (Int, Int) => Int = _ + _
68
val safeAdd = Lift.liftO(add) // (Option[Int], Option[Int]) => Option[Int]
69
val result = safeAdd(Some(5), Some(3)) // Some(8)
70
val failed = safeAdd(Some(5), None) // None
71
72
// Generic transformations with SYB
73
case class Person(name: String, age: Int)
74
val person = Person("Alice", 30)
75
76
object incrementAge extends Poly1 {
77
implicit def caseInt = at[Int](_ + 1)
78
implicit def default[T] = at[T](identity)
79
}
80
81
val olderPerson = person.everywhere(incrementAge)
82
// Person("Alice", 31) - age incremented, name unchanged
83
```
84
85
## Architecture
86
87
Shapeless is built around several core concepts that work together to enable generic programming:
88
89
- **HList (Heterogeneous Lists)**: Statically typed lists that can contain elements of different types, providing the foundation for type-safe generic operations
90
- **Type-level Programming**: Natural numbers (`Nat`) and type-level computations enable compile-time verification and manipulation
91
- **Polymorphic Functions**: `Poly` functions allow type-specific behavior while maintaining a single interface
92
- **Isomorphisms**: Bidirectional type transformations (`Iso`) enable safe conversions between equivalent representations
93
- **Automatic Derivation**: Type class instances can be automatically derived for case classes and other algebraic data types
94
- **Compile-time Safety**: All operations are verified at compile time, eliminating runtime type errors
95
96
## Capabilities
97
98
### Heterogeneous Lists (HList)
99
100
Core data structure for holding statically typed sequences of different types. Provides rich operations for manipulation, transformation, and type-safe access.
101
102
```scala { .api }
103
sealed trait HList
104
final case class ::[+H, +T <: HList](head: H, tail: T) extends HList
105
case object HNil extends HNil
106
107
// Enhanced operations through HListOps
108
class HListOps[L <: HList](l: L) {
109
def head(implicit c: IsHCons[L]): c.H
110
def tail(implicit c: IsHCons[L]): c.T
111
def ::[H](h: H): H :: L
112
def ++[S <: HList](suffix: S)(implicit prepend: Prepend[L, S]): prepend.Out
113
def reverse(implicit reverse: Reverse[L]): reverse.Out
114
def map[HF](f: HF)(implicit mapper: Mapper[HF, L]): mapper.Out
115
}
116
```
117
118
[HList Operations](./hlist.md)
119
120
### Polymorphic Functions
121
122
Type-safe polymorphic functions that can have different behavior for different types while maintaining a unified interface.
123
124
```scala { .api }
125
trait Poly extends Product with Serializable
126
127
trait Poly1 extends Poly {
128
def at[T] = new Case1Builder[T]
129
def apply[T](t: T)(implicit c: Case1[T]): c.R
130
}
131
132
// Natural transformations
133
trait ~>[F[_], G[_]] extends Poly1 {
134
def apply[T](f: F[T]): G[T]
135
}
136
```
137
138
[Polymorphic Functions](./poly.md)
139
140
### Type-level Natural Numbers
141
142
Compile-time natural number arithmetic for expressing constraints and computations at the type level.
143
144
```scala { .api }
145
trait Nat
146
case class Succ[P <: Nat]() extends Nat
147
class _0 extends Nat
148
149
// Arithmetic operations
150
trait Sum[A <: Nat, B <: Nat] { type Out <: Nat }
151
trait Diff[A <: Nat, B <: Nat] { type Out <: Nat }
152
trait Prod[A <: Nat, B <: Nat] { type Out <: Nat }
153
154
// Comparison operations
155
trait LT[A <: Nat, B <: Nat]
156
type <[A <: Nat, B <: Nat] = LT[A, B]
157
```
158
159
[Type-level Natural Numbers](./nat.md)
160
161
### Type-safe Records
162
163
HList-based records with compile-time field access and type-safe manipulation operations.
164
165
```scala { .api }
166
trait Field[T] extends FieldAux { type valueType = T }
167
type FieldEntry[F <: FieldAux] = (F, F#valueType)
168
169
class RecordOps[L <: HList](l: L) {
170
def get[F <: FieldAux](f: F)(implicit selector: Selector[L, FieldEntry[F]]): F#valueType
171
def updated[V, F <: Field[V]](f: F, v: V)(implicit updater: Updater[L, F, V]): updater.Out
172
def +[V, F <: Field[V]](fv: (F, V))(implicit updater: Updater[L, F, V]): updater.Out
173
def -[F <: FieldAux](f: F)(implicit remove: Remove[FieldEntry[F], L]): remove.Out
174
}
175
```
176
177
[Type-safe Records](./records.md)
178
179
### Type-safe Conversions
180
181
Bidirectional conversions between tuples, functions, and HLists with compile-time safety guarantees.
182
183
```scala { .api }
184
// Tuple to HList conversion
185
trait HLister[T <: Product] { type Out <: HList; def apply(t: T): Out }
186
187
// Function conversions
188
trait FnHLister[F] { type Out; def apply(f: F): Out }
189
trait FnUnHLister[F] { type Out; def apply(f: F): Out }
190
191
// Traversable conversions
192
trait FromTraversable[Out <: HList] {
193
def apply(l: GenTraversable[_]): Option[Out]
194
}
195
```
196
197
[Type-safe Conversions](./conversions.md)
198
199
### Generic Programming Utilities
200
201
Advanced utilities for functional references, navigation, and transformations including lenses, zippers, and isomorphisms.
202
203
```scala { .api }
204
// Lenses for functional references
205
trait Lens[C, F] {
206
def get(c: C): F
207
def set(c: C)(f: F): C
208
def modify(c: C)(f: F => F): C
209
}
210
211
// Zippers for navigation and updates
212
case class Zipper[C, L <: HList, R <: HList, P](prefix: L, suffix: R, parent: P)
213
214
// Isomorphisms for bidirectional conversions
215
trait Iso[T, U] {
216
def to(t: T): U
217
def from(u: U): T
218
def reverse: Iso[U, T]
219
}
220
```
221
222
[Generic Programming Utilities](./generic.md)
223
224
### Sized Collections
225
226
Collections with statically known size, providing compile-time length verification and safe operations.
227
228
```scala { .api }
229
abstract class Sized[+Repr, L <: Nat](r: Repr) {
230
type A
231
def unsized = r
232
}
233
234
class SizedOps[A, Repr, L <: Nat] {
235
def head(implicit ev: _0 < L): A
236
def tail(implicit pred: Pred[L]): Sized[Repr, pred.Out]
237
def take[M <: Nat](implicit diff: Diff[L, M]): Sized[Repr, M]
238
def drop[M <: Nat](implicit diff: Diff[L, M]): Sized[Repr, diff.Out]
239
}
240
```
241
242
[Sized Collections](./sized.md)
243
244
### Type Operators and Advanced Types
245
246
Type-level logic operations, inequalities, tagged types, and advanced type programming constructs.
247
248
```scala { .api }
249
type Id[+T] = T
250
type ¬[T] = T => Nothing
251
type ∧[T, U] = T with U
252
type ∨[T, U] = ¬[¬[T] ∧ ¬[U]]
253
254
// Type inequalities
255
trait =:!=[A, B] // Type inequality witness
256
trait <:!<[A, B] // Subtype inequality witness
257
258
// Tagged types
259
type @@[T, U] = T with Tagged[U]
260
```
261
262
[Type Operators](./typeoperators.md)
263
264
### Runtime Type Safety
265
266
Runtime type-safe casting with compile-time guarantees through the `Typeable` type class.
267
268
```scala { .api }
269
trait Typeable[U] {
270
def cast(t: Any): Option[U]
271
}
272
273
class Cast(t: Any) {
274
def cast[U](implicit castU: Typeable[U]): Option[U]
275
}
276
```
277
278
[Runtime Type Safety](./typeable.md)
279
280
### Heterogeneous Maps (HMap)
281
282
Type-safe maps where keys and values can have different types, with relationships enforced at compile time through type-level relations.
283
284
```scala { .api }
285
class HMap[R[_, _]](underlying: Map[Any, Any] = Map.empty) extends Poly {
286
def get[K, V](k: K)(implicit ev: R[K, V]): Option[V]
287
def +[K, V](kv: (K, V))(implicit ev: R[K, V]): HMap[R]
288
def -[K](k: K): HMap[R]
289
}
290
```
291
292
[Heterogeneous Maps](./hmap.md)
293
294
### Function Lifting
295
296
Utilities for lifting ordinary functions of arbitrary arity into various contexts such as Option, enabling safe handling of potentially absent values.
297
298
```scala { .api }
299
object Lift {
300
def liftO[InF, InL <: HList, R, OInL <: HList, OutF](f: InF): OutF
301
}
302
```
303
304
[Function Lifting](./lift.md)
305
306
### Scrap Your Boilerplate (SYB)
307
308
Generic programming combinators for queries and transformations over arbitrarily nested data structures without explicit recursion.
309
310
```scala { .api }
311
trait Data[F, T, R] {
312
def gmapQ(f: F, t: T): R
313
}
314
315
trait DataT[F, T] {
316
def gmapT(f: F, t: T): T
317
}
318
319
def everything[F <: Poly](f: F): ...
320
def everywhere[F <: Poly](f: F): ...
321
```
322
323
[Scrap Your Boilerplate](./sybclass.md)