0
# HLists and Heterogeneous Collections
1
2
Heterogeneous Lists (HLists) are the fundamental data structure in Shapeless, allowing you to create lists that contain elements of different types while preserving complete type information at compile time. They provide the foundation for most other Shapeless operations.
3
4
## Capabilities
5
6
### Core HList Types
7
8
The basic building blocks for creating and working with heterogeneous lists.
9
10
```scala { .api }
11
// Base trait for all heterogeneous lists
12
sealed trait HList
13
14
// Cons cell - non-empty HList with head H and tail T
15
final case class ::[+H, +T <: HList](head: H, tail: T) extends HList
16
17
// Empty HList
18
sealed trait HNil extends HList
19
case object HNil extends HNil
20
21
// Type aliases for convenience
22
type HNil = shapeless.HNil
23
type ::[H, T <: HList] = shapeless.::[H, T]
24
```
25
26
### HList Construction
27
28
Multiple ways to create HLists from various data sources.
29
30
```scala { .api }
31
object HList {
32
// Create empty HList
33
def apply(): HNil.type
34
35
// Create single-element HList
36
def apply[T](t: T): T :: HNil
37
38
// Create HList from Product (tuple, case class)
39
def apply[P <: Product, L <: HList](p: P)(implicit gen: Generic.Aux[P, L]): L
40
41
// Fill HList with n repeated elements
42
def fill[A](n: Nat)(elem: A)(implicit fill: Fill[n.N, A]): fill.Out
43
44
// Fill 2D HList structure
45
def fill[A](n1: Nat, n2: Nat)(elem: A)(implicit fill: Fill[(n1.N, n2.N), A]): fill.Out
46
47
// Fill with polymorphic function - returns FillWithOps
48
def fillWith[L <: HList]: FillWithOps[L]
49
}
50
51
// Helper class for fillWith operation
52
final class FillWithOps[L <: HList] {
53
def apply[F <: Poly](f: F)(implicit fillWith: FillWith[F, L]): L
54
}
55
56
// Cons operator - infix syntax for construction
57
def ::[H, T <: HList](head: H, tail: T): H :: T
58
59
// Construction examples:
60
val empty: HNil = HNil
61
val single: String :: HNil = "hello" :: HNil
62
val mixed: String :: Int :: Boolean :: HNil = "test" :: 42 :: true :: HNil
63
```
64
65
### Element Access and Selection
66
67
Type-safe access to HList elements by position or type.
68
69
```scala { .api }
70
// Access by compile-time position
71
trait At[L <: HList, N <: Nat] {
72
type Out
73
def apply(l: L): Out
74
}
75
76
// Select element by type (must be unique)
77
trait Selector[L <: HList, U] {
78
def apply(l: L): U
79
}
80
81
// Check if HList is non-empty and get head/tail
82
trait IsHCons[L <: HList] {
83
type H
84
type T <: HList
85
def head(l: L): H
86
def tail(l: L): T
87
}
88
89
// Usage examples:
90
val hlist = "test" :: 42 :: true :: HNil
91
val head: String = hlist.head
92
val tail = hlist.tail
93
val byType: Int = hlist.select[Int]
94
val byPos = hlist.at(Nat._1) // Gets element at position 1 (42)
95
```
96
97
### Structure Operations
98
99
Operations that modify the structure of HLists.
100
101
```scala { .api }
102
// Get length as compile-time natural number
103
trait Length[L <: HList] {
104
type Out <: Nat
105
def apply(l: L): Out
106
}
107
108
// Prepend one HList to another
109
trait Prepend[P <: HList, S <: HList] {
110
type Out <: HList
111
def apply(prefix: P, suffix: S): Out
112
}
113
114
// Reverse HList
115
trait Reverse[L <: HList] {
116
type Out <: HList
117
def apply(l: L): Out
118
}
119
120
// Take first N elements
121
trait Take[L <: HList, N <: Nat] {
122
type Out <: HList
123
def apply(l: L): Out
124
}
125
126
// Drop first N elements
127
trait Drop[L <: HList, N <: Nat] {
128
type Out <: HList
129
def apply(l: L): Out
130
}
131
132
// Split at position N
133
trait Split[L <: HList, N <: Nat] {
134
type Prefix <: HList
135
type Suffix <: HList
136
def apply(l: L): (Prefix, Suffix)
137
}
138
```
139
140
### Functional Operations
141
142
Map, fold, and other functional operations over HLists.
143
144
```scala { .api }
145
// Map polymorphic function over HList
146
trait Mapper[L <: HList, F] {
147
type Out <: HList
148
def apply(l: L, f: F): Out
149
}
150
151
// Flat map operation
152
trait FlatMapper[L <: HList, F] {
153
type Out <: HList
154
def apply(l: L, f: F): Out
155
}
156
157
// Fold with polymorphic function
158
trait Folder[L <: HList, V, F] {
159
type Out
160
def apply(l: L, v: V, f: F): Out
161
}
162
163
// Left fold with binary function
164
trait LeftFolder[L <: HList, V, F] {
165
type Out
166
def apply(l: L, v: V, f: F): Out
167
}
168
169
// Right fold with binary function
170
trait RightFolder[L <: HList, V, F] {
171
type Out
172
def apply(l: L, v: V, f: F): Out
173
}
174
175
// Usage example:
176
object addOne extends Poly1 {
177
implicit def caseInt = at[Int](_ + 1)
178
implicit def caseString = at[String](_ + "!")
179
}
180
181
val mapped = hlist.map(addOne) // "test!" :: 43 :: true :: HNil
182
```
183
184
### Zipper Operations
185
186
Zip and unzip operations for working with pairs and tuples.
187
188
```scala { .api }
189
// Zip HList of pairs into pair of HLists
190
trait Zip[L <: HList] {
191
type Out
192
def apply(l: L): Out
193
}
194
195
// Unzip pair of HLists into HList of pairs
196
trait Unzip[L] {
197
type Out <: HList
198
def apply(l: L): Out
199
}
200
201
// Apply HList of functions to HList of arguments
202
trait ZipApply[L <: HList, A <: HList] {
203
type Out <: HList
204
def apply(fl: L, al: A): Out
205
}
206
207
// Example:
208
val pairs = ("a", 1) :: ("b", 2) :: HNil
209
val (strings, ints) = pairs.unzip // ("a" :: "b" :: HNil, 1 :: 2 :: HNil)
210
```
211
212
### Type Constraints
213
214
Apply constraints to all elements of an HList.
215
216
```scala { .api }
217
// All elements satisfy constraint C
218
trait Constraint[L <: HList, C[_]] {
219
type Out <: HList
220
def apply(l: L): Out
221
}
222
223
// Unary type constructor constraint
224
trait UnaryTCConstraint[L <: HList, C[_]] {
225
type Out <: HList
226
def apply(l: L, f: C ~> Id): Out
227
}
228
229
// Collect evidence for constraint
230
trait BasisConstraint[L <: HList, M[_]] {
231
type Out <: HList
232
def apply(l: L): Out
233
}
234
235
// Example - ensure all elements are Numeric
236
def sumAll[L <: HList](l: L)(implicit
237
constraint: Constraint[L, Numeric],
238
folder: LeftFolder[L, Int, +.type]
239
): folder.Out = l.foldLeft(0)(+)
240
```
241
242
### Conversion Operations
243
244
Convert HLists to and from other data structures.
245
246
```scala { .api }
247
// Convert HList to corresponding tuple
248
trait Tupler[L <: HList] {
249
type Out
250
def apply(l: L): Out
251
}
252
253
// Convert to generic representation
254
trait Generic[T] {
255
type Repr
256
def to(t: T): Repr
257
def from(repr: Repr): T
258
}
259
260
// Examples:
261
val hlist = "test" :: 42 :: true :: HNil
262
val tuple: (String, Int, Boolean) = hlist.tupled
263
val backToHList = tuple.productElements // Extension method from shapeless
264
```
265
266
### Sized Collections
267
268
Collections with compile-time known size information.
269
270
```scala { .api }
271
// Wrapper for collections with size information
272
final case class Sized[Repr, L <: Nat](unsized: Repr)
273
274
// Create sized collection
275
def sized[CC[_], T, N <: Nat](cc: CC[T])(implicit
276
s: AdditiveCollection[CC[T], T, CC[T]],
277
l: Length.Aux[CC[T], N]
278
): Sized[CC[T], N]
279
280
// Operations preserve size information
281
trait SizedMap[Repr, L <: Nat, A, B] {
282
type Out
283
def apply(s: Sized[Repr, L], f: A => B): Sized[Out, L]
284
}
285
286
// Usage:
287
val sizedList = Sized[List[Int], Nat._3](List(1, 2, 3))
288
val mapped = sizedList.map(_ * 2) // Still Sized[List[Int], Nat._3]
289
```
290
291
## Usage Examples
292
293
### Basic HList Operations
294
295
```scala
296
import shapeless._
297
298
// Create HList
299
val data = "Alice" :: 30 :: true :: HNil
300
301
// Access elements
302
val name: String = data.head
303
val rest = data.tail
304
val age: Int = data.select[Int]
305
val status: Boolean = data.at(Nat._2)
306
307
// Modify structure
308
val extended = false :: data // Boolean :: String :: Int :: Boolean :: HNil
309
val shortened = data.take(Nat._2) // String :: Int :: HNil
310
val reversed = data.reverse // Boolean :: Int :: String :: HNil
311
```
312
313
### Polymorphic Function Mapping
314
315
```scala
316
object stringify extends Poly1 {
317
implicit def default[T] = at[T](_.toString)
318
implicit def caseString = at[String](s => s"'$s'")
319
}
320
321
val mixed = "test" :: 42 :: true :: HNil
322
val strings = mixed.map(stringify) // "'test'" :: "42" :: "true" :: HNil
323
```
324
325
### Converting Between Products and HLists
326
327
```scala
328
case class Person(name: String, age: Int, active: Boolean)
329
330
val person = Person("Bob", 25, false)
331
val gen = Generic[Person]
332
333
// Convert to HList
334
val hlist = gen.to(person) // String :: Int :: Boolean :: HNil
335
336
// Modify using HList operations
337
val older = hlist.updatedBy[Int](_ + 1)
338
val modified = gen.from(older) // Person("Bob", 26, false)
339
```