0
# Type-safe Conversions
1
2
Shapeless provides type-safe bidirectional conversions between various Scala data structures including tuples, functions, and HLists. These conversions maintain type safety and enable seamless interoperability between different representations of the same data.
3
4
## Tuple ↔ HList Conversions
5
6
### HLister Type Class
7
8
```scala { .api }
9
/**
10
* Converts Products (tuples) to HLists
11
*/
12
trait HLister[T <: Product] {
13
type Out <: HList
14
def apply(t: T): Out
15
}
16
```
17
18
### Tupler Type Class
19
20
```scala { .api }
21
/**
22
* Converts HLists to tuples (when possible)
23
*/
24
trait Tupler[L <: HList] {
25
type Out <: Product
26
def apply(l: L): Out
27
}
28
```
29
30
### Tuples Object
31
32
```scala { .api }
33
/**
34
* Provides enhanced operations for tuple conversions
35
*/
36
object Tuples {
37
trait TupleOps[L <: HList] {
38
def hlisted: L
39
}
40
41
implicit def tupleOps[T <: Product](t: T)(implicit hlister: HLister[T]): TupleOps[hlister.Out]
42
43
object hlisted extends Poly1 // Converts Tuples to HLists
44
object tupled extends Poly1 // Converts HLists to Tuples
45
}
46
```
47
48
**Usage Examples:**
49
50
```scala
51
import shapeless._
52
import syntax.std.tuple._
53
54
// Convert tuple to HList
55
val tuple = (42, "hello", true)
56
val hlist = tuple.hlisted // Int :: String :: Boolean :: HNil
57
58
// Convert HList to tuple
59
val backToTuple = hlist.tupled // (Int, String, Boolean) = (42, "hello", true)
60
61
// Polymorphic conversion
62
import Tuples._
63
64
val pairs = ((1, "a"), (2, "b"), (3, "c"))
65
val hlistPairs = (pairs._1.hlisted, pairs._2.hlisted, pairs._3.hlisted)
66
// (1 :: "a" :: HNil, 2 :: "b" :: HNil, 3 :: "c" :: HNil)
67
```
68
69
### Product Arity
70
71
```scala { .api }
72
/**
73
* Witnesses the arity of a Product type
74
*/
75
trait ProductArity[P <: Product] {
76
type N <: Nat // Arity as natural number
77
}
78
```
79
80
**Usage Examples:**
81
82
```scala
83
import shapeless._
84
85
// Get arity information at type level
86
def showArity[P <: Product, N <: Nat]
87
(p: P)
88
(implicit arity: ProductArity.Aux[P, N], toInt: ToInt[N]): Int = toInt()
89
90
val pair = (1, "hello")
91
val triple = (1, "hello", true)
92
93
val pairArity = showArity(pair) // 2
94
val tripleArity = showArity(triple) // 3
95
```
96
97
## Function Conversions
98
99
### Function to HList Function
100
101
```scala { .api }
102
/**
103
* Converts ordinary functions to HList functions
104
*/
105
trait FnHLister[F] {
106
type Out
107
def apply(f: F): Out
108
}
109
```
110
111
### HList Function to Function
112
113
```scala { .api }
114
/**
115
* Converts HList functions to ordinary functions
116
*/
117
trait FnUnHLister[F] {
118
type Out
119
def apply(f: F): Out
120
}
121
```
122
123
### Functions Object
124
125
```scala { .api }
126
/**
127
* Provides enhanced operations for function conversions
128
*/
129
object Functions {
130
trait FnHListOps[HLFn] {
131
def hlisted: HLFn
132
}
133
134
trait FnUnHListOps[F] {
135
def unhlisted: F
136
}
137
}
138
```
139
140
**Usage Examples:**
141
142
```scala
143
import shapeless._
144
import syntax.std.function._
145
146
// Convert regular function to HList function
147
val add: (Int, Int) => Int = _ + _
148
val hlistAdd = add.hlisted // (Int :: Int :: HNil) => Int
149
150
// Apply HList function
151
val args = 5 :: 3 :: HNil
152
val result = hlistAdd(args) // 8
153
154
// Convert back to regular function
155
val regularAdd = hlistAdd.unhlisted // (Int, Int) => Int
156
val result2 = regularAdd(7, 2) // 9
157
158
// More complex example
159
val processData: (String, Int, Boolean) => String =
160
(name, age, active) => s"$name is $age years old and ${if (active) "active" else "inactive"}"
161
162
val hlistProcessor = processData.hlisted
163
val data = "Alice" :: 25 :: true :: HNil
164
val processed = hlistProcessor(data) // "Alice is 25 years old and active"
165
```
166
167
### Function Arity Support
168
169
Shapeless provides function conversion support for functions of arity 0 through 22:
170
171
```scala
172
import shapeless._
173
import syntax.std.function._
174
175
// Nullary function
176
val getValue: () => String = () => "constant"
177
val hlistGetValue = getValue.hlisted // HNil => String
178
val value = hlistGetValue(HNil) // "constant"
179
180
// Unary function
181
val double: Int => Int = _ * 2
182
val hlistDouble = double.hlisted // (Int :: HNil) => Int
183
val doubled = hlistDouble(5 :: HNil) // 10
184
185
// Higher arity functions
186
val combine5: (Int, String, Boolean, Double, Char) => String =
187
(i, s, b, d, c) => s"$i-$s-$b-$d-$c"
188
189
val hlistCombine5 = combine5.hlisted
190
val args5 = 42 :: "test" :: true :: 3.14 :: 'x' :: HNil
191
val combined = hlistCombine5(args5) // "42-test-true-3.14-x"
192
```
193
194
## Traversable Conversions
195
196
### FromTraversable Type Class
197
198
```scala { .api }
199
/**
200
* Type-safe conversion from Traversables to HLists
201
*/
202
trait FromTraversable[Out <: HList] {
203
def apply(l: GenTraversable[_]): Option[Out]
204
}
205
```
206
207
### Traversables Object
208
209
```scala { .api }
210
/**
211
* Provides enhanced operations for traversable conversions
212
*/
213
object Traversables {
214
trait TraversableOps {
215
def toHList[L <: HList](implicit fl: FromTraversable[L]): Option[L]
216
}
217
}
218
```
219
220
**Usage Examples:**
221
222
```scala
223
import shapeless._
224
import syntax.std.traversable._
225
226
// Convert List to HList with specific type structure
227
val mixedList: List[Any] = List(42, "hello", true)
228
val hlistOpt: Option[Int :: String :: Boolean :: HNil] = mixedList.toHList
229
230
hlistOpt match {
231
case Some(hlist) =>
232
val i: Int = hlist.head // 42
233
val s: String = hlist.tail.head // "hello"
234
val b: Boolean = hlist.tail.tail.head // true
235
case None =>
236
println("Conversion failed - type mismatch")
237
}
238
239
// Safe conversion - returns None on type mismatch
240
val wrongTypes: List[Any] = List("not", "an", "int")
241
val failedConversion: Option[Int :: String :: Boolean :: HNil] = wrongTypes.toHList
242
// failedConversion: None
243
244
// Convert collections of uniform type
245
val numbers = List(1, 2, 3)
246
val numberHList: Option[Int :: Int :: Int :: HNil] = numbers.toHList
247
```
248
249
## Generic Conversions
250
251
### Automatic Conversions
252
253
Shapeless provides automatic conversions between equivalent representations:
254
255
```scala
256
import shapeless._
257
258
// Case class to HList (requires Generic)
259
case class Person(name: String, age: Int, active: Boolean)
260
261
val person = Person("Bob", 30, true)
262
// Conversion to HList happens through Generic (covered in generic.md)
263
264
// Automatic tuple/HList interop in operations
265
val tuple = (1, "hello", true)
266
val hlist = 42 :: "world" :: false :: HNil
267
268
// Can zip tuple with HList (after conversion)
269
val tupleAsHList = tuple.hlisted
270
val zipped = tupleAsHList.zip(hlist)
271
// (1, 42) :: ("hello", "world") :: (true, false) :: HNil
272
```
273
274
### Bidirectional Operations
275
276
```scala
277
import shapeless._
278
import syntax.std.tuple._
279
280
// Complex conversion pipeline
281
val data = ((1, "a"), (2, "b"), (3, "c"))
282
283
// Convert tuple of tuples to HList of HLists
284
val step1 = data.hlisted // (1, "a") :: (2, "b") :: (3, "c") :: HNil
285
val step2 = step1.map(hlisted) // Would require proper Poly1 setup
286
287
// Practical example with functions
288
val processor: ((String, Int)) => String = { case (name, age) => s"$name:$age" }
289
val hlistProcessor = processor.hlisted // ((String :: Int :: HNil)) => String
290
291
val inputs = ("Alice" :: 25 :: HNil) :: ("Bob" :: 30 :: HNil) :: HNil
292
// Process each input (would need proper mapping setup)
293
```
294
295
## Advanced Conversion Patterns
296
297
### Conditional Conversions
298
299
```scala
300
import shapeless._
301
302
// Convert only when types match exactly
303
def safeConvert[T <: Product, L <: HList]
304
(product: T)
305
(implicit hlister: HLister.Aux[T, L]): L = hlister(product)
306
307
val tuple2 = (42, "hello")
308
val tuple3 = (42, "hello", true)
309
310
val hlist2 = safeConvert(tuple2) // Int :: String :: HNil
311
val hlist3 = safeConvert(tuple3) // Int :: String :: Boolean :: HNil
312
```
313
314
### Type-preserving Operations
315
316
```scala
317
import shapeless._
318
import syntax.std.tuple._
319
import syntax.std.function._
320
321
// Round-trip conversion maintains types
322
def roundTrip[T <: Product, L <: HList]
323
(t: T)
324
(implicit
325
hlister: HLister.Aux[T, L],
326
tupler: Tupler.Aux[L, T]): T = {
327
328
val hlist = t.hlisted
329
// Could perform HList operations here
330
hlist.tupled
331
}
332
333
val original = (1, "test", true)
334
val afterRoundTrip = roundTrip(original)
335
// afterRoundTrip has same type and value as original
336
```
337
338
### Conversion Validation
339
340
```scala
341
import shapeless._
342
343
// Validate conversions at compile time
344
def validateConversion[T <: Product, L <: HList, T2 <: Product]
345
(t: T)
346
(implicit
347
hlister: HLister.Aux[T, L],
348
tupler: Tupler.Aux[L, T2],
349
ev: T =:= T2): T2 = {
350
351
t.hlisted.tupled
352
}
353
354
// This ensures the round-trip conversion preserves exact types
355
val validated = validateConversion((1, "hello"))
356
// Type is exactly (Int, String)
357
```
358
359
### Integration with Collections
360
361
```scala
362
import shapeless._
363
import syntax.std.tuple._
364
import syntax.std.traversable._
365
366
// Convert collection of tuples to collection of HLists
367
val tupleList = List((1, "a"), (2, "b"), (3, "c"))
368
val hlistList = tupleList.map(_.hlisted)
369
// List[Int :: String :: HNil]
370
371
// Process and convert back
372
val processedHLists = hlistList.map(_.reverse)
373
val backToTuples = processedHLists.map(_.tupled)
374
// List[("a", 1), ("b", 2), ("c", 3)]
375
```
376
377
Type-safe conversions in shapeless enable seamless interoperability between different data representations while maintaining compile-time safety and type inference. This allows you to choose the most appropriate representation for each operation while avoiding runtime type errors.