or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-utilities.mdcoproduct-unions.mdgeneric-derivation.mdhlist-collections.mdindex.mdoptics-lenses.mdpoly-typelevel.mdrecords-fields.md

coproduct-unions.mddocs/

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

```