0
# Polymorphic Functions
1
2
Polymorphic functions in shapeless allow you to define functions that can have different behavior for different types while maintaining a unified interface. This enables type-safe generic programming where the same function name can perform type-specific operations.
3
4
## Core Abstractions
5
6
### Base Poly Trait
7
8
```scala { .api }
9
/**
10
* Base trait for polymorphic functions
11
*/
12
trait Poly extends Product with Serializable {
13
type Case0[T] = Case0Aux[this.type, T]
14
type Case1[T] = Case1Aux[this.type, T]
15
type Case2[T, U] = Case2Aux[this.type, T, U]
16
17
def apply[T](implicit c: Case0[T]): T
18
def apply[T](t: T)(implicit c: Case1[T]): c.R
19
def apply[T, U](t: T, u: U)(implicit c: Case2[T, U]): c.R
20
}
21
```
22
23
### Poly0 - Polymorphic Values
24
25
```scala { .api }
26
/**
27
* Polymorphic values (nullary functions) - different values for different types
28
*/
29
trait Poly0 extends Poly {
30
def at[T](v: T) = new Case0[T] { val value = v }
31
}
32
```
33
34
**Usage Examples:**
35
36
```scala
37
import shapeless._
38
39
object zero extends Poly0 {
40
implicit val zeroInt = at[Int](0)
41
implicit val zeroDouble = at[Double](0.0)
42
implicit val zeroString = at[String]("")
43
implicit def zeroList[T] = at[List[T]](Nil)
44
}
45
46
val intZero: Int = zero[Int] // 0
47
val stringZero: String = zero[String] // ""
48
val listZero: List[String] = zero[List[String]] // Nil
49
```
50
51
### Poly1 - Unary Polymorphic Functions
52
53
```scala { .api }
54
/**
55
* Polymorphic unary functions - different implementations for different input types
56
*/
57
trait Poly1 extends Poly {
58
def at[T] = new Case1Builder[T]
59
60
class Case1Builder[T] {
61
def apply[R0](f: T => R0) = new Case1[T] {
62
type R = R0
63
val value = f
64
}
65
}
66
}
67
```
68
69
**Usage Examples:**
70
71
```scala
72
import shapeless._
73
74
object size extends Poly1 {
75
implicit def caseInt = at[Int](identity)
76
implicit def caseString = at[String](_.length)
77
implicit def caseList[T] = at[List[T]](_.length)
78
implicit def caseOption[T] = at[Option[T]](_.fold(0)(_ => 1))
79
}
80
81
val intSize: Int = size(42) // 42
82
val stringSize: Int = size("hello") // 5
83
val listSize: Int = size(List(1, 2, 3)) // 3
84
val optionSize: Int = size(Some("x")) // 1
85
86
// Use with HList.map
87
val hlist = 42 :: "world" :: List(1, 2) :: HNil
88
val sizes = hlist.map(size) // 42 :: 5 :: 2 :: HNil
89
```
90
91
### Poly2 - Binary Polymorphic Functions
92
93
```scala { .api }
94
/**
95
* Polymorphic binary functions - different implementations for different input type combinations
96
*/
97
trait Poly2 extends Poly {
98
def at[T, U] = new Case2Builder[T, U]
99
100
class Case2Builder[T, U] {
101
def apply[R0](f: (T, U) => R0) = new Case2[T, U] {
102
type R = R0
103
val value = f
104
}
105
}
106
}
107
```
108
109
**Usage Examples:**
110
111
```scala
112
import shapeless._
113
114
object plus extends Poly2 {
115
implicit val caseInt = at[Int, Int](_ + _)
116
implicit val caseDouble = at[Double, Double](_ + _)
117
implicit val caseString = at[String, String](_ + _)
118
implicit def caseList[T] = at[List[T], List[T]](_ ::: _)
119
}
120
121
val sumInt = plus(5, 3) // 8
122
val sumString = plus("hello", "world") // "helloworld"
123
val sumList = plus(List(1, 2), List(3, 4)) // List(1, 2, 3, 4)
124
125
// Use with HList operations
126
val left = 1 :: "a" :: List(1) :: HNil
127
val right = 2 :: "b" :: List(2) :: HNil
128
val result = left.zip(right).map(plus) // 3 :: "ab" :: List(1, 2) :: HNil
129
```
130
131
## Natural Transformations
132
133
### Natural Transformation (~>)
134
135
```scala { .api }
136
/**
137
* Natural transformation from type constructor F to G
138
*/
139
trait ~>[F[_], G[_]] extends Poly1 {
140
def apply[T](f: F[T]): G[T]
141
}
142
```
143
144
**Usage Examples:**
145
146
```scala
147
import shapeless._
148
149
object optionToList extends (Option ~> List) {
150
def apply[T](o: Option[T]): List[T] = o.toList
151
}
152
153
object listToOption extends (List ~> Option) {
154
def apply[T](l: List[T]): Option[T] = l.headOption
155
}
156
157
val someValue: Option[Int] = Some(42)
158
val asList: List[Int] = optionToList(someValue) // List(42)
159
val backToOption: Option[Int] = listToOption(asList) // Some(42)
160
161
// Use with HList of different container types
162
val containers = Some(1) :: List("a", "b") :: Some(true) :: HNil
163
val allLists = containers.map(optionToList) // List(1) :: List("a", "b") :: List(true) :: HNil
164
```
165
166
### Constant Natural Transformation (~>>)
167
168
```scala { .api }
169
/**
170
* Natural transformation from type constructor F to constant type R
171
*/
172
trait ~>>[F[_], R] extends Pullback1[R] {
173
def apply[T](f: F[T]): R
174
}
175
```
176
177
**Usage Examples:**
178
179
```scala
180
import shapeless._
181
182
object isEmpty extends (Option ~>> Boolean) {
183
def apply[T](o: Option[T]): Boolean = o.isEmpty
184
}
185
186
object size extends (List ~>> Int) {
187
def apply[T](l: List[T]): Int = l.size
188
}
189
190
val checks = Some(42) :: None :: Some("hello") :: HNil
191
val results = checks.map(isEmpty) // false :: true :: false :: HNil
192
```
193
194
## Predefined Polymorphic Functions
195
196
### Identity Functions
197
198
```scala { .api }
199
/**
200
* Identity natural transformation
201
*/
202
object identity extends (Id ~> Id) {
203
def apply[T](t: T) = t
204
}
205
```
206
207
### Option Operations
208
209
```scala { .api }
210
/**
211
* Wrap values in Option
212
*/
213
object option extends (Id ~> Option) {
214
def apply[T](t: T) = Option(t)
215
}
216
217
/**
218
* Extract values from Option (unsafe)
219
*/
220
object get extends (Option ~> Id) {
221
def apply[T](o: Option[T]) = o.get
222
}
223
224
/**
225
* Check if Option is defined
226
*/
227
object isDefined extends (Option ~>> Boolean) {
228
def apply[T](o: Option[T]) = o.isDefined
229
}
230
```
231
232
### Collection Operations
233
234
```scala { .api }
235
/**
236
* Create singleton Set
237
*/
238
object singleton extends (Id ~> Set) {
239
def apply[T](t: T) = Set(t)
240
}
241
242
/**
243
* Choose element from Set
244
*/
245
object choose extends (Set ~> Option) {
246
def apply[T](s: Set[T]) = s.headOption
247
}
248
249
/**
250
* Create singleton List
251
*/
252
object list extends (Id ~> List) {
253
def apply[T](t: T) = List(t)
254
}
255
256
/**
257
* Get head of List as Option
258
*/
259
object headOption extends (List ~> Option) {
260
def apply[T](l: List[T]) = l.headOption
261
}
262
```
263
264
**Usage Examples:**
265
266
```scala
267
import shapeless._
268
269
val values = 1 :: "hello" :: true :: HNil
270
val options = values.map(option) // Some(1) :: Some("hello") :: Some(true) :: HNil
271
val sets = values.map(singleton) // Set(1) :: Set("hello") :: Set(true) :: HNil
272
val lists = values.map(list) // List(1) :: List("hello") :: List(true) :: HNil
273
val defined = options.map(isDefined) // true :: true :: true :: HNil
274
```
275
276
## Function Lifting
277
278
### Lifting Function1 to Poly1
279
280
```scala { .api }
281
/**
282
* Lifts a Function1 to Poly1 with subtyping support
283
*/
284
class ->[T, R](f: T => R) extends Poly1 {
285
implicit def subT[U <: T] = at[U](f)
286
}
287
```
288
289
**Usage Examples:**
290
291
```scala
292
import shapeless._
293
294
val addOne = new ->[Int, Int](_ + 1)
295
val toString = new ->[Any, String](_.toString)
296
297
val numbers = 1 :: 2 :: 3 :: HNil
298
val incremented = numbers.map(addOne) // 2 :: 3 :: 4 :: HNil
299
300
val mixed = 42 :: "hello" :: true :: HNil
301
val strings = mixed.map(toString) // "42" :: "hello" :: "true" :: HNil
302
```
303
304
### Lifting to HList Range
305
306
```scala { .api }
307
/**
308
* Lifts Function1 to Poly1 over universal domain, results wrapped in HList
309
*/
310
class >->[T, R](f: T => R) extends LowPriorityLiftFunction1 {
311
implicit def subT[U <: T] = at[U](t => f(t) :: HNil)
312
}
313
```
314
315
## Advanced Usage Patterns
316
317
### Composing Polymorphic Functions
318
319
```scala
320
import shapeless._
321
322
object increment extends Poly1 {
323
implicit def caseInt = at[Int](_ + 1)
324
implicit def caseDouble = at[Double](_ + 1.0)
325
}
326
327
object stringify extends Poly1 {
328
implicit def caseInt = at[Int](_.toString)
329
implicit def caseDouble = at[Double](_.toString)
330
}
331
332
// Chain polymorphic operations
333
val numbers = 5 :: 2.5 :: 10 :: HNil
334
val incremented = numbers.map(increment) // 6 :: 3.5 :: 11 :: HNil
335
val strings = incremented.map(stringify) // "6" :: "3.5" :: "11" :: HNil
336
```
337
338
### Type-specific Behavior
339
340
```scala
341
import shapeless._
342
343
object processValue extends Poly1 {
344
implicit def caseString = at[String](s => s"Processed string: $s")
345
implicit def caseInt = at[Int](i => s"Processed number: ${i * 2}")
346
implicit def caseBoolean = at[Boolean](b => s"Processed boolean: ${!b}")
347
implicit def caseList[T] = at[List[T]](l => s"Processed list of ${l.size} items")
348
}
349
350
val mixed = "hello" :: 42 :: true :: List(1, 2, 3) :: HNil
351
val processed = mixed.map(processValue)
352
// "Processed string: hello" :: "Processed number: 84" ::
353
// "Processed boolean: false" :: "Processed list of 3 items" :: HNil
354
```
355
356
### Conditional Processing
357
358
```scala
359
import shapeless._
360
361
object safeHead extends Poly1 {
362
implicit def caseList[T] = at[List[T]](_.headOption)
363
implicit def caseOption[T] = at[Option[T]](identity)
364
implicit def caseString = at[String](s => if (s.nonEmpty) Some(s.head) else None)
365
}
366
367
val collections = List(1, 2, 3) :: "" :: "hello" :: Some(42) :: None :: HNil
368
val heads = collections.map(safeHead)
369
// Some(1) :: None :: Some('h') :: Some(42) :: None :: HNil
370
```
371
372
## Integration with HList Operations
373
374
Polymorphic functions integrate seamlessly with HList operations:
375
376
```scala
377
import shapeless._
378
379
// Map over HList elements
380
val data = 1 :: "hello" :: List(1, 2) :: HNil
381
val sizes = data.map(size) // 1 :: 5 :: 2 :: HNil
382
383
// Fold with polymorphic operations
384
val sum = sizes.foldLeft(0)(plus) // 8
385
386
// Filter and transform
387
val mixed = 1 :: 2.5 :: "test" :: 3 :: HNil
388
val numbers = mixed.filter[Int] ++ mixed.filter[Double] // 1 :: 3 :: 2.5 :: HNil
389
val doubled = numbers.map(plus) // Error: need two arguments
390
391
// Zip and apply
392
val left = 1 :: "a" :: List(1) :: HNil
393
val right = 2 :: "b" :: List(2) :: HNil
394
val combined = left.zip(right).map(plus) // 3 :: "ab" :: List(1, 2) :: HNil
395
```
396
397
This polymorphic function system forms the backbone of shapeless's type-safe generic programming capabilities, allowing you to write functions that adapt their behavior based on the types they encounter while maintaining compile-time safety.