0
# Coproducts and Union Types
1
2
Coproducts in Shapeless provide type-safe sum types representing "one of" relationships. They're the dual concept to HLists, modeling exclusive alternatives rather than sequential combinations. Coproducts enable exhaustive pattern matching and safe union operations with compile-time guarantees.
3
4
## Capabilities
5
6
### Core Coproduct Types
7
8
The fundamental types for creating and working with type-safe unions.
9
10
```scala { .api }
11
// Base trait for all coproducts (sum types)
12
sealed trait Coproduct
13
14
// Coproduct constructor - either H or T
15
sealed trait :+:[+H, +T <: Coproduct] extends Coproduct {
16
def eliminate[A](f: H => A, g: T => A): A
17
}
18
19
// Left injection - value is of type H
20
final case class Inl[+H, +T <: Coproduct](head: H) extends (H :+: T)
21
22
// Right injection - value is from remaining alternatives T
23
final case class Inr[+H, +T <: Coproduct](tail: T) extends (H :+: T)
24
25
// Empty coproduct - uninhabited/impossible type
26
sealed trait CNil extends Coproduct {
27
def impossible: Nothing // Used in exhaustive pattern matching
28
}
29
```
30
31
### Coproduct Construction
32
33
Methods for creating coproduct instances from values.
34
35
```scala { .api }
36
object Coproduct {
37
// Factory class for creating coproduct instances with type class injection
38
class MkCoproduct[C <: Coproduct] {
39
def apply[T](t: T)(implicit inj: Inject[C, T]): C
40
}
41
42
// Get MkCoproduct instance
43
def apply[C <: Coproduct]: MkCoproduct[C]
44
45
// Unsafe construction - builds nested Inr structure
46
def unsafeMkCoproduct(length: Int, value: Any): Coproduct
47
48
// Unsafe extraction - gets value from nested structure
49
def unsafeGet(c: Coproduct): Any
50
}
51
52
// Usage examples:
53
type StringOrInt = String :+: Int :+: CNil
54
val str: StringOrInt = Inl("hello")
55
val num: StringOrInt = Inr(Inl(42))
56
```
57
58
### Injection and Selection
59
60
Type-safe injection of values into coproducts and selection from coproducts.
61
62
```scala { .api }
63
// Inject type I into coproduct C
64
trait Inject[C <: Coproduct, I] {
65
def apply(i: I): C
66
}
67
68
// Select/extract type T from coproduct C
69
trait Selector[C <: Coproduct, T] {
70
def apply(c: C): Option[T]
71
}
72
73
// Access coproduct alternative at position N
74
trait At[C <: Coproduct, N <: Nat] {
75
type Out
76
def apply(c: C): Option[Out]
77
}
78
79
// Get compile-time length of coproduct
80
trait Length[C <: Coproduct] {
81
type Out <: Nat
82
def apply(c: C): Out
83
}
84
85
// Usage:
86
val union: String :+: Int :+: Boolean :+: CNil = Coproduct[String :+: Int :+: Boolean :+: CNil]("test")
87
val extracted: Option[String] = union.select[String]
88
val atPos: Option[String] = union.at(Nat._0)
89
```
90
91
### Coproduct Transformations
92
93
Operations for mapping and transforming coproduct values.
94
95
```scala { .api }
96
// Map polymorphic function over coproduct
97
trait Mapper[F, C <: Coproduct] {
98
type Out <: Coproduct
99
def apply(f: F, c: C): Out
100
}
101
102
// Unify coproduct to common supertype
103
trait Unifier[C <: Coproduct] {
104
type Out
105
def apply(c: C): Out
106
}
107
108
// Extend coproduct with additional type
109
trait ExtendRight[C <: Coproduct, A] {
110
type Out <: Coproduct
111
def apply(c: C): Out
112
}
113
114
// Usage example:
115
object toUpperCase extends Poly1 {
116
implicit def caseString = at[String](_.toUpperCase)
117
implicit def caseInt = at[Int](_.toString.toUpperCase)
118
}
119
120
val mapped = coproduct.map(toUpperCase)
121
```
122
123
### Pattern Matching and Elimination
124
125
Safe pattern matching and value extraction from coproducts.
126
127
```scala { .api }
128
// Eliminate coproduct by providing handlers for each case
129
def eliminate[A](c: String :+: Int :+: CNil)(
130
stringHandler: String => A,
131
intHandler: Int => A
132
): A
133
134
// Fold operation for coproducts
135
trait Folder[F, C <: Coproduct] {
136
type Out
137
def apply(f: F, c: C): Out
138
}
139
140
// Usage:
141
val result = union.eliminate(
142
(s: String) => s.length,
143
(i: Int) => i * 2,
144
(b: Boolean) => if (b) 1 else 0
145
)
146
```
147
148
### Union Types
149
150
Higher-level union type abstractions built on coproducts.
151
152
```scala { .api }
153
// Union type operations
154
object Union {
155
// Create union from value
156
def apply[U <: Coproduct]: UnionInstantiator[U]
157
158
// Pattern matching support
159
trait UnionMatcher[U <: Coproduct] {
160
def apply[A](u: U)(cases: PartialFunction[Any, A]): A
161
}
162
}
163
164
// Example usage:
165
type MyUnion = String :+: Int :+: Boolean :+: CNil
166
167
val union1: MyUnion = Union[MyUnion]("hello")
168
val union2: MyUnion = Union[MyUnion](42)
169
170
val result = union1 match {
171
case s: String => s.length
172
case i: Int => i * 2
173
case b: Boolean => if (b) 1 else 0
174
}
175
```
176
177
### Coproduct Operations
178
179
Additional operations for working with coproduct structures.
180
181
```scala { .api }
182
// Remove type T from coproduct C
183
trait Remove[C <: Coproduct, T] {
184
type Out <: Coproduct
185
def apply(c: C): Either[T, Out]
186
}
187
188
// Split coproduct at position N
189
trait Split[C <: Coproduct, N <: Nat] {
190
type Prefix <: Coproduct
191
type Suffix <: Coproduct
192
def apply(c: C): Either[Prefix, Suffix]
193
}
194
195
// Reverse coproduct structure
196
trait Reverse[C <: Coproduct] {
197
type Out <: Coproduct
198
def apply(c: C): Out
199
}
200
201
// Rotate coproduct alternatives
202
trait RotateLeft[C <: Coproduct, N <: Nat] {
203
type Out <: Coproduct
204
def apply(c: C): Out
205
}
206
```
207
208
### Integration with HLists
209
210
Operations that work between coproducts and HLists.
211
212
```scala { .api }
213
// Apply HList of functions to coproduct
214
trait ZipApply[L <: HList, C <: Coproduct] {
215
type Out <: Coproduct
216
def apply(fl: L, c: C): Out
217
}
218
219
// Convert between HList and Coproduct representations
220
trait ToHList[C <: Coproduct] {
221
type Out <: HList
222
def apply(c: C): Out
223
}
224
225
trait FromHList[L <: HList] {
226
type Out <: Coproduct
227
def apply(l: L): Out
228
}
229
```
230
231
## Usage Examples
232
233
### Basic Coproduct Operations
234
235
```scala
236
import shapeless._
237
238
// Define coproduct type
239
type Response = String :+: Int :+: Boolean :+: CNil
240
241
// Create instances
242
val stringResponse: Response = Inl("success")
243
val intResponse: Response = Inr(Inl(200))
244
val boolResponse: Response = Inr(Inr(Inl(true)))
245
246
// Pattern match and extract
247
val message = stringResponse.select[String] // Some("success")
248
val code = intResponse.select[Int] // Some(200)
249
val flag = stringResponse.select[Boolean] // None
250
251
// Eliminate with handlers
252
val result = stringResponse.eliminate(
253
(s: String) => s"Message: $s",
254
(i: Int) => s"Code: $i",
255
(b: Boolean) => s"Flag: $b"
256
)
257
```
258
259
### Polymorphic Function Mapping
260
261
```scala
262
object processResponse extends Poly1 {
263
implicit def caseString = at[String](s => s"Processed: $s")
264
implicit def caseInt = at[Int](i => s"Status: $i")
265
implicit def caseBoolean = at[Boolean](b => s"Active: $b")
266
}
267
268
val processed = stringResponse.map(processResponse)
269
// Results in String :+: Int :+: Boolean :+: CNil with transformed value
270
```
271
272
### Generic Coproduct Derivation
273
274
```scala
275
sealed trait Color
276
case object Red extends Color
277
case object Green extends Color
278
case object Blue extends Color
279
280
// Automatically derive coproduct representation
281
val gen = Generic[Color]
282
type ColorRepr = gen.Repr // Red.type :+: Green.type :+: Blue.type :+: CNil
283
284
val red: Color = Red
285
val repr = gen.to(red) // Coproduct representation
286
val back = gen.from(repr) // Back to Color
287
```
288
289
### Safe Union Operations
290
291
```scala
292
// Define union of possible error types
293
sealed trait DatabaseError
294
case class ConnectionError(message: String) extends DatabaseError
295
case class QueryError(sql: String, error: String) extends DatabaseError
296
case class TimeoutError(duration: Int) extends DatabaseError
297
298
type ErrorUnion = ConnectionError :+: QueryError :+: TimeoutError :+: CNil
299
300
def handleError(error: ErrorUnion): String = error.eliminate(
301
(ce: ConnectionError) => s"Connection failed: ${ce.message}",
302
(qe: QueryError) => s"Query failed: ${qe.sql} - ${qe.error}",
303
(te: TimeoutError) => s"Timeout after ${te.duration}ms"
304
)
305
306
// Type-safe injection
307
val connectionError: ErrorUnion = Coproduct[ErrorUnion](ConnectionError("Network unreachable"))
308
val message = handleError(connectionError)
309
```
310
311
### Runtime Type Switching
312
313
```scala
314
def processValue(value: Any): Option[String :+: Int :+: Boolean :+: CNil] = {
315
value match {
316
case s: String => Some(Inl(s))
317
case i: Int => Some(Inr(Inl(i)))
318
case b: Boolean => Some(Inr(Inr(Inl(b))))
319
case _ => None
320
}
321
}
322
323
val result = processValue("hello")
324
// Some(Inl("hello")) with type Option[String :+: Int :+: Boolean :+: CNil]
325
```