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

generic-derivation.mddocs/

0

# Generic Programming and Automatic Derivation

1

2

Generic programming in Shapeless enables automatic derivation of type class instances by converting between user-defined types and generic representations. This is the core value proposition of Shapeless - eliminating boilerplate code while maintaining complete type safety through compile-time transformations.

3

4

## Capabilities

5

6

### Generic Type Class

7

8

The fundamental type class that establishes bidirectional conversion between concrete types and their generic representations.

9

10

```scala { .api }

11

trait Generic[A] {

12

// Associated type for the generic representation

13

type Repr

14

15

// Convert from concrete type to generic representation

16

def to(a: A): Repr

17

18

// Convert from generic representation back to concrete type

19

def from(repr: Repr): A

20

}

21

22

object Generic {

23

// Type alias with fixed representation type

24

type Aux[A, Repr0] = Generic[A] { type Repr = Repr0 }

25

26

// Summoner method to get Generic instance

27

def apply[A](implicit gen: Generic[A]): Generic.Aux[A, gen.Repr]

28

29

// Manually create Generic instance

30

def instance[A, R](to: A => R, from: R => A): Generic.Aux[A, R]

31

32

// Macro-generated instances for case classes and sealed traits

33

implicit def materialize[A, R]: Generic.Aux[A, R] = macro GenericMacros.materialize[A, R]

34

}

35

```

36

37

### LabelledGeneric Type Class

38

39

Enhanced version of Generic that preserves field names and constructor names as singleton types in the representation.

40

41

```scala { .api }

42

trait LabelledGeneric[A] {

43

// Associated type preserving labels as singleton types

44

type Repr

45

46

// Convert to labeled generic representation

47

def to(a: A): Repr

48

49

// Convert from labeled representation back to concrete type

50

def from(repr: Repr): A

51

}

52

53

object LabelledGeneric {

54

// Type alias with fixed representation

55

type Aux[A, Repr0] = LabelledGeneric[A] { type Repr = Repr0 }

56

57

// Summoner method

58

def apply[A](implicit lgen: LabelledGeneric[A]): LabelledGeneric.Aux[A, lgen.Repr]

59

60

// Separate materialization for products and coproducts

61

implicit def materializeProduct[A, L <: HList]: LabelledGeneric.Aux[A, L] =

62

macro LabelledGenericMacros.materializeProduct[A, L]

63

64

implicit def materializeCoproduct[A, C <: Coproduct]: LabelledGeneric.Aux[A, C] =

65

macro LabelledGenericMacros.materializeCoproduct[A, C]

66

}

67

```

68

69

### Type Class Witnesses

70

71

Type classes that provide evidence about the generic structure of types.

72

73

```scala { .api }

74

// Witnesses that T has product (case class/tuple) representation

75

trait HasProductGeneric[T] {

76

type Repr <: HList

77

}

78

79

// Witnesses that T has coproduct (sealed trait) representation

80

trait HasCoproductGeneric[T] {

81

type Repr <: Coproduct

82

}

83

84

// Witnesses that T corresponds to a tuple type

85

trait IsTuple[T] {

86

type Repr <: HList

87

}

88

89

// Get evidence for product structure

90

def productGeneric[T](implicit hpg: HasProductGeneric[T]): HasProductGeneric.Aux[T, hpg.Repr]

91

92

// Get evidence for coproduct structure

93

def coproductGeneric[T](implicit hcg: HasCoproductGeneric[T]): HasCoproductGeneric.Aux[T, hcg.Repr]

94

```

95

96

### Higher-Kinded Generic (Generic1)

97

98

Generic programming for type constructors (types with one type parameter).

99

100

```scala { .api }

101

trait Generic1[F[_], FR[_[_]]] {

102

// Convert type constructor to generic representation

103

def to[A](fa: F[A]): FR[({ type λ[B] = A })#λ][A]

104

105

// Convert from generic representation

106

def from[A](fra: FR[({ type λ[B] = A })#λ][A]): F[A]

107

}

108

109

// Example for Option

110

trait OptionGeneric extends Generic1[Option, ({ type λ[G[_]] = G[Nothing] :+: (G ~> G) :+: CNil })#λ]

111

```

112

113

### Automatic Derivation Infrastructure

114

115

Supporting type classes for automatic instance derivation.

116

117

```scala { .api }

118

// Lazy evaluation for recursive derivation

119

trait Lazy[T] {

120

val value: T

121

}

122

123

object Lazy {

124

// Create lazy instance

125

implicit def apply[T](implicit t: => T): Lazy[T] = new Lazy[T] {

126

lazy val value: T = t

127

}

128

}

129

130

// Cached implicit resolution

131

trait Cached[T] {

132

val value: T

133

}

134

135

object Cached {

136

implicit def apply[T](implicit t: T): Cached[T] = new Cached[T] {

137

val value: T = t

138

}

139

}

140

141

// Default value provision

142

trait Default[T] {

143

def apply(): T

144

}

145

146

object Default {

147

// Default for Option is None

148

implicit def optionDefault[T]: Default[Option[T]] = () => None

149

150

// Automatic derivation for case classes

151

implicit def genericDefault[A, L <: HList](implicit

152

gen: Generic.Aux[A, L],

153

defaults: Default[L]

154

): Default[A] = () => gen.from(defaults())

155

}

156

```

157

158

### Derivation Annotations

159

160

Annotations to control generic derivation behavior.

161

162

```scala { .api }

163

// Exclude field or method from generic derivation

164

class nonGeneric extends scala.annotation.StaticAnnotation

165

166

// Usage:

167

case class Person(

168

name: String,

169

age: Int,

170

@nonGeneric internal: String = "hidden"

171

)

172

// Generic representation excludes 'internal' field

173

```

174

175

## Usage Examples

176

177

### Basic Generic Derivation

178

179

```scala

180

import shapeless._

181

182

// Define case class

183

case class Person(name: String, age: Int, active: Boolean)

184

185

// Get generic instance

186

val gen = Generic[Person]

187

// gen.Repr is String :: Int :: Boolean :: HNil

188

189

val person = Person("Alice", 30, true)

190

191

// Convert to generic representation

192

val repr = gen.to(person)

193

// repr: String :: Int :: Boolean :: HNil = "Alice" :: 30 :: true :: HNil

194

195

// Convert back to case class

196

val restored = gen.from(repr)

197

// restored: Person = Person("Alice", 30, true)

198

```

199

200

### Labeled Generic with Field Names

201

202

```scala

203

import shapeless._, labelled.FieldType

204

205

case class Book(title: String, pages: Int, published: Boolean)

206

207

val lgen = LabelledGeneric[Book]

208

// lgen.Repr preserves field names as singleton types

209

210

val book = Book("1984", 328, true)

211

val labeled = lgen.to(book)

212

// Result has type: FieldType['title, String] :: FieldType['pages, Int] :: FieldType['published, Boolean] :: HNil

213

214

// Access with field names preserved

215

val title = labeled.select[FieldType['title, String]]

216

val pages = labeled.select[FieldType['pages, Int]]

217

```

218

219

### Generic Derivation for Sealed Traits

220

221

```scala

222

sealed trait Shape

223

case class Circle(radius: Double) extends Shape

224

case class Rectangle(width: Double, height: Double) extends Shape

225

case class Triangle(base: Double, height: Double) extends Shape

226

227

val shapeGen = Generic[Shape]

228

// shapeGen.Repr is Circle :+: Rectangle :+: Triangle :+: CNil

229

230

val circle: Shape = Circle(5.0)

231

val circleRepr = shapeGen.to(circle)

232

// circleRepr: Circle :+: Rectangle :+: Triangle :+: CNil = Inl(Circle(5.0))

233

234

val rect: Shape = shapeGen.from(Inr(Inl(Rectangle(10.0, 20.0))))

235

// rect: Shape = Rectangle(10.0, 20.0)

236

```

237

238

### Automatic Type Class Derivation

239

240

```scala

241

// Define type class

242

trait Show[T] {

243

def show(t: T): String

244

}

245

246

// Base instances

247

implicit val stringShow: Show[String] = identity

248

implicit val intShow: Show[Int] = _.toString

249

implicit val boolShow: Show[Boolean] = _.toString

250

251

// HList instances

252

implicit val hnilShow: Show[HNil] = _ => ""

253

254

implicit def hconsShow[H, T <: HList](implicit

255

hShow: Show[H],

256

tShow: Show[T]

257

): Show[H :: T] = {

258

case h :: t => s"${hShow.show(h)} :: ${tShow.show(t)}"

259

}

260

261

// Automatic derivation for case classes

262

implicit def genericShow[A, R](implicit

263

gen: Generic.Aux[A, R],

264

rShow: Show[R]

265

): Show[A] = a => rShow.show(gen.to(a))

266

267

// Usage - Show instance automatically derived

268

case class Employee(name: String, id: Int, active: Boolean)

269

val emp = Employee("Bob", 123, true)

270

println(emp.show) // Outputs: "Bob :: 123 :: true :: "

271

```

272

273

### Recursive Derivation with Lazy

274

275

```scala

276

// Recursive data structure

277

sealed trait Tree[A]

278

case class Leaf[A](value: A) extends Tree[A]

279

case class Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A]

280

281

// Define type class for tree processing

282

trait TreeSize[T] {

283

def size(t: T): Int

284

}

285

286

// Base instances

287

implicit val leafSize: TreeSize[Leaf[_]] = _ => 1

288

289

implicit def branchSize[A](implicit

290

treeSize: Lazy[TreeSize[Tree[A]]] // Lazy for recursive reference

291

): TreeSize[Branch[A]] = branch =>

292

1 + treeSize.value.size(branch.left) + treeSize.value.size(branch.right)

293

294

// Generic derivation for Tree

295

implicit def treeSize[A](implicit

296

gen: Generic[Tree[A]],

297

reprSize: Lazy[TreeSize[gen.Repr]]

298

): TreeSize[Tree[A]] = tree => reprSize.value.size(gen.to(tree))

299

300

// Usage

301

val tree: Tree[Int] = Branch(

302

Leaf(1),

303

Branch(Leaf(2), Leaf(3))

304

)

305

println(tree.size) // Outputs: 5 (3 leaves + 2 branches)

306

```

307

308

### Default Value Derivation

309

310

```scala

311

case class Config(

312

host: String,

313

port: Int,

314

debug: Boolean,

315

timeout: Option[Int]

316

)

317

318

// Derive default instance automatically

319

implicit val stringDefault: Default[String] = () => "localhost"

320

implicit val intDefault: Default[Int] = () => 8080

321

implicit val boolDefault: Default[Boolean] = () => false

322

323

val defaultConfig = Default[Config].apply()

324

// Config("localhost", 8080, false, None)

325

```

326

327

### Working with Tuples

328

329

```scala

330

// Tuples have automatic Generic instances

331

val tuple = ("hello", 42, true)

332

val tupleGen = Generic[(String, Int, Boolean)]

333

334

val tupleAsHList = tupleGen.to(tuple)

335

// tupleAsHList: String :: Int :: Boolean :: HNil

336

337

// Transform via HList operations

338

val modified = tupleAsHList.updatedBy[String](_.toUpperCase)

339

val backToTuple = tupleGen.from(modified)

340

// backToTuple: (String, Int, Boolean) = ("HELLO", 42, true)

341

```

342

343

### Custom Generic Instances

344

345

```scala

346

// Define custom type

347

class CustomContainer[A](val items: List[A]) {

348

def get: List[A] = items

349

}

350

351

// Manual Generic instance

352

implicit def customGeneric[A]: Generic.Aux[CustomContainer[A], List[A] :: HNil] =

353

Generic.instance(

354

to = container => container.items :: HNil,

355

from = {

356

case items :: HNil => new CustomContainer(items)

357

}

358

)

359

360

// Now CustomContainer works with generic derivation

361

val container = new CustomContainer(List(1, 2, 3))

362

val repr = Generic[CustomContainer[Int]].to(container)

363

// repr: List[Int] :: HNil = List(1, 2, 3) :: HNil

364

```