or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

conversions.mdgeneric.mdhlist.mdhmap.mdindex.mdlift.mdnat.mdpoly.mdrecords.mdsized.mdsybclass.mdtypeable.mdtypeoperators.md

sybclass.mddocs/

0

# Scrap Your Boilerplate

1

2

Shapeless provides an implementation of "Scrap Your Boilerplate with Class" that enables generic queries and transformations over arbitrarily nested data structures. This allows you to write functions that work uniformly across different data types without explicit recursion.

3

4

## Core Type Classes

5

6

### Data - Generic Queries

7

8

```scala { .api }

9

/**

10

* Type class for generic queries over data structures

11

* F - query function type, T - data type, R - result type

12

*/

13

trait Data[F, T, R] {

14

def gmapQ(f: F, t: T): R

15

}

16

```

17

18

### DataT - Generic Transformations

19

20

```scala { .api }

21

/**

22

* Type class for generic transformations over data structures

23

* F - transformation function type, T - data type

24

*/

25

trait DataT[F, T] {

26

def gmapT(f: F, t: T): T

27

}

28

```

29

30

## Generic Combinators

31

32

### Everything - Generic Queries

33

34

The `everything` combinator applies a query function everywhere in a data structure and combines results:

35

36

```scala { .api }

37

/**

38

* Apply query function everywhere and combine results

39

*/

40

def everything[F <: Poly](f: F)(implicit ...): ...

41

```

42

43

**Usage Examples:**

44

45

```scala

46

import shapeless._

47

48

case class Person(name: String, age: Int, address: Address)

49

case class Address(street: String, city: String, zip: String)

50

case class Company(name: String, employees: List[Person])

51

52

val person = Person("Alice", 30, Address("123 Main St", "Boston", "02101"))

53

val company = Company("Acme Corp", List(

54

Person("Bob", 25, Address("456 Oak Ave", "Cambridge", "02139")),

55

Person("Carol", 35, Address("789 Pine St", "Somerville", "02144"))

56

))

57

58

// Query to count all strings in a data structure

59

object countStrings extends Poly1 {

60

implicit def caseString = at[String](_ => 1)

61

implicit def default[T] = at[T](_ => 0)

62

}

63

64

// Count strings in person record

65

val personStringCount = person.everything(countStrings)

66

// Result: counts "Alice", "123 Main St", "Boston", "02101" = 4

67

68

// Count strings in company structure

69

val companyStringCount = company.everything(countStrings)

70

// Result: counts all string fields across the entire structure

71

```

72

73

### Everywhere - Generic Transformations

74

75

The `everywhere` combinator applies a transformation function everywhere in a data structure:

76

77

```scala { .api }

78

/**

79

* Apply transformation function everywhere in data structure

80

*/

81

def everywhere[F <: Poly](f: F)(implicit ...): ...

82

```

83

84

**Usage Examples:**

85

86

```scala

87

import shapeless._

88

89

// Transformation to uppercase all strings

90

object uppercaseStrings extends Poly1 {

91

implicit def caseString = at[String](_.toUpperCase)

92

implicit def default[T] = at[T](identity)

93

}

94

95

val person = Person("Alice", 30, Address("123 Main St", "Boston", "02101"))

96

97

val uppercasedPerson = person.everywhere(uppercaseStrings)

98

// Result: Person("ALICE", 30, Address("123 MAIN ST", "BOSTON", "02101"))

99

100

// Transformation to increment all integers

101

object incrementInts extends Poly1 {

102

implicit def caseInt = at[Int](_ + 1)

103

implicit def default[T] = at[T](identity)

104

}

105

106

val incrementedPerson = person.everywhere(incrementInts)

107

// Result: Person("Alice", 31, Address("123 Main St", "Boston", "02101"))

108

```

109

110

## Advanced Query Patterns

111

112

### Selective Queries

113

114

```scala

115

import shapeless._

116

117

case class Product(id: Int, name: String, price: Double, categories: List[String])

118

case class Order(id: Int, products: List[Product], total: Double)

119

120

val order = Order(1001, List(

121

Product(1, "Widget", 19.99, List("tools", "hardware")),

122

Product(2, "Gadget", 29.99, List("electronics", "gadgets"))

123

), 49.98)

124

125

// Query to find all prices

126

object findPrices extends Poly1 {

127

implicit def caseDouble = at[Double](d => List(d))

128

implicit def default[T] = at[T](_ => List.empty[Double])

129

}

130

131

val allPrices = order.everything(findPrices)

132

// Result: List(19.99, 29.99, 49.98) - all Double values

133

134

// Query to collect all strings longer than 5 characters

135

object findLongStrings extends Poly1 {

136

implicit def caseString = at[String](s => if (s.length > 5) List(s) else List.empty)

137

implicit def default[T] = at[T](_ => List.empty[String])

138

}

139

140

val longStrings = order.everything(findLongStrings)

141

// Result: List("Widget", "Gadget", "electronics", "gadgets", "hardware")

142

```

143

144

### Statistical Queries

145

146

```scala

147

import shapeless._

148

149

// Query to compute statistics

150

object computeStats extends Poly1 {

151

case class Stats(count: Int, sum: Double, min: Double, max: Double)

152

153

implicit def caseDouble = at[Double](d => Stats(1, d, d, d))

154

implicit def caseInt = at[Int](i => Stats(1, i.toDouble, i.toDouble, i.toDouble))

155

implicit def default[T] = at[T](_ => Stats(0, 0.0, Double.MaxValue, Double.MinValue))

156

157

// Monoid for combining stats

158

implicit val statsMonoid = new Monoid[Stats] {

159

def zero = Stats(0, 0.0, Double.MaxValue, Double.MinValue)

160

def append(a: Stats, b: Stats) = Stats(

161

a.count + b.count,

162

a.sum + b.sum,

163

math.min(a.min, b.min),

164

math.max(a.max, b.max)

165

)

166

}

167

}

168

169

val stats = order.everything(computeStats)

170

// Result: Stats with count, sum, min, max of all numeric values

171

```

172

173

## Advanced Transformation Patterns

174

175

### Conditional Transformations

176

177

```scala

178

import shapeless._

179

180

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

181

case class Department(name: String, users: List[User])

182

183

val dept = Department("Engineering", List(

184

User(1, "alice", "alice@example.com", true),

185

User(2, "bob", "bob@example.com", false),

186

User(3, "carol", "carol@example.com", true)

187

))

188

189

// Transform to normalize names and emails

190

object normalizeData extends Poly1 {

191

implicit def caseString = at[String] { s =>

192

if (s.contains("@")) s.toLowerCase // Email addresses

193

else s.split(" ").map(_.capitalize).mkString(" ") // Names

194

}

195

implicit def default[T] = at[T](identity)

196

}

197

198

val normalizedDept = dept.everywhere(normalizeData)

199

// Result: names capitalized, emails lowercased

200

201

// Transform to anonymize sensitive data in inactive users

202

object anonymizeInactive extends Poly1 {

203

implicit def caseUser = at[User] { user =>

204

if (!user.active) user.copy(name = "REDACTED", email = "REDACTED")

205

else user

206

}

207

implicit def default[T] = at[T](identity)

208

}

209

210

val anonymizedDept = dept.everywhere(anonymizeInactive)

211

// Result: inactive users have name and email redacted

212

```

213

214

### Type-specific Transformations

215

216

```scala

217

import shapeless._

218

219

case class Document(title: String, content: String, tags: List[String], wordCount: Int)

220

case class Section(heading: String, documents: List[Document])

221

case class Library(name: String, sections: List[Section])

222

223

val library = Library("Technical Library", List(

224

Section("Programming", List(

225

Document("Scala Guide", "Scala is...", List("scala", "programming"), 1500),

226

Document("Haskell Intro", "Haskell is...", List("haskell", "functional"), 2000)

227

)),

228

Section("Mathematics", List(

229

Document("Linear Algebra", "Vectors and...", List("math", "algebra"), 3000)

230

))

231

))

232

233

// Transform to update word counts and normalize tags

234

object updateLibrary extends Poly1 {

235

implicit def caseDocument = at[Document] { doc =>

236

val actualWordCount = doc.content.split("\\s+").length

237

val normalizedTags = doc.tags.map(_.toLowerCase.trim)

238

doc.copy(wordCount = actualWordCount, tags = normalizedTags)

239

}

240

241

implicit def caseString = at[String](_.trim) // Trim whitespace from strings

242

implicit def default[T] = at[T](identity)

243

}

244

245

val updatedLibrary = library.everywhere(updateLibrary)

246

// Result: word counts updated, tags normalized, strings trimmed

247

```

248

249

## Custom Data Type Support

250

251

To use SYB with custom data types, you need to provide appropriate type class instances:

252

253

```scala

254

import shapeless._

255

256

case class Tree[T](value: T, children: List[Tree[T]])

257

258

// Provide Data instance for Tree

259

implicit def treeData[F <: Poly, T, R](implicit

260

fT: F.Case1.Aux[T, R],

261

dataList: Data[F, List[Tree[T]], R],

262

monoid: Monoid[R]

263

): Data[F, Tree[T], R] = new Data[F, Tree[T], R] {

264

def gmapQ(f: F, tree: Tree[T]): R = {

265

val valueResult = f(tree.value)

266

val childrenResult = dataList.gmapQ(f, tree.children)

267

monoid.append(valueResult, childrenResult)

268

}

269

}

270

271

// Provide DataT instance for Tree

272

implicit def treeDataT[F <: Poly, T](implicit

273

fT: F.Case1.Aux[T, T],

274

dataListT: DataT[F, List[Tree[T]]]

275

): DataT[F, Tree[T]] = new DataT[F, Tree[T]] {

276

def gmapT(f: F, tree: Tree[T]): Tree[T] = {

277

val newValue = f(tree.value)

278

val newChildren = dataListT.gmapT(f, tree.children)

279

Tree(newValue, newChildren)

280

}

281

}

282

283

val tree = Tree("root", List(

284

Tree("child1", List(Tree("leaf1", Nil), Tree("leaf2", Nil))),

285

Tree("child2", List(Tree("leaf3", Nil)))

286

))

287

288

// Now can use everything/everywhere with Tree

289

val stringCount = tree.everything(countStrings)

290

val uppercased = tree.everywhere(uppercaseStrings)

291

```

292

293

## Integration with Other Shapeless Features

294

295

SYB works well with other shapeless features:

296

297

```scala

298

import shapeless._

299

300

// Use with HLists

301

val data = 42 :: "hello" :: true :: 3.14 :: HNil

302

val stringCount = data.everything(countStrings) // 1 (for "hello")

303

val uppercased = data.everywhere(uppercaseStrings) // 42 :: "HELLO" :: true :: 3.14 :: HNil

304

305

// Use with Records

306

val record = ("name" ->> "Alice") :: ("age" ->> 30) :: ("active" ->> true) :: HNil

307

val recordStringCount = record.everything(countStrings)

308

val recordUppercased = record.everywhere(uppercaseStrings)

309

```

310

311

Scrap Your Boilerplate provides powerful generic programming capabilities that eliminate the need for writing repetitive traversal code, enabling concise and type-safe operations over complex nested data structures.