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

records.mddocs/

0

# Type-safe Records

1

2

Shapeless records provide type-safe, compile-time verified record operations built on top of HLists. Records allow you to work with labeled data structures where field access, updates, and manipulations are all verified at compile time.

3

4

## Core Types

5

6

### Field Definition

7

8

```scala { .api }

9

/**

10

* Field trait representing a typed field key

11

*/

12

trait Field[T] extends FieldAux {

13

type valueType = T

14

}

15

16

/**

17

* Base trait for field keys

18

*/

19

trait FieldAux {

20

type valueType

21

}

22

```

23

24

### Field Entry

25

26

```scala { .api }

27

/**

28

* A field entry pairs a field with its value

29

*/

30

type FieldEntry[F <: FieldAux] = (F, F#valueType)

31

```

32

33

### Record Type Alias

34

35

Records are HLists of FieldEntry pairs:

36

37

```scala { .api }

38

// A record is an HList of field entries

39

type Record = HList // where elements are FieldEntry[F] for various F

40

```

41

42

## Creating Records

43

44

### Field Declaration Syntax

45

46

```scala { .api }

47

/**

48

* Syntax for creating field keys

49

*/

50

implicit class FieldOps[K](k: K) {

51

def ->>[V](v: V): FieldEntry[Field[V]] = ???

52

}

53

```

54

55

**Usage Examples:**

56

57

```scala

58

import shapeless._

59

import record._

60

61

// Create field keys and records

62

val nameField = "name" ->> "John Doe"

63

val ageField = "age" ->> 30

64

val activeField = "active" ->> true

65

66

// Combine into record

67

val person = nameField :: ageField :: activeField :: HNil

68

// Type: FieldEntry[Field[String]] :: FieldEntry[Field[Int]] :: FieldEntry[Field[Boolean]] :: HNil

69

70

// Directly create records

71

val book = ("title" ->> "Shapeless Guide") :: ("pages" ->> 300) :: ("isbn" ->> "978-0123456789") :: HNil

72

val product = ("name" ->> "Widget") :: ("price" ->> 19.99) :: ("inStock" ->> true) :: HNil

73

```

74

75

## Record Operations

76

77

### RecordOps Enhancement

78

79

Records are enhanced with specialized operations through `RecordOps[L <: HList]`:

80

81

```scala { .api }

82

/**

83

* Enhanced operations for HList records

84

*/

85

class RecordOps[L <: HList](l: L) {

86

def get[F <: FieldAux](f: F)(implicit selector: Selector[L, FieldEntry[F]]): F#valueType

87

def updated[V, F <: Field[V]](f: F, v: V)(implicit updater: Updater[L, F, V]): updater.Out

88

def remove[F <: FieldAux](f: F)(implicit remove: Remove[FieldEntry[F], L]): (F#valueType, remove.Out)

89

def +[V, F <: Field[V]](fv: (F, V))(implicit updater: Updater[L, F, V]): updater.Out

90

def -[F <: FieldAux](f: F)(implicit remove: Remove[FieldEntry[F], L]): remove.Out

91

}

92

```

93

94

### Field Access

95

96

```scala { .api }

97

/**

98

* Type-safe field access by field key

99

*/

100

def get[F <: FieldAux](f: F)(implicit selector: Selector[L, FieldEntry[F]]): F#valueType

101

```

102

103

**Usage Examples:**

104

105

```scala

106

import shapeless._

107

import record._

108

109

val person = ("name" ->> "Alice") :: ("age" ->> 25) :: ("city" ->> "Boston") :: HNil

110

111

// Type-safe field access

112

val name: String = person("name") // "Alice"

113

val age: Int = person("age") // 25

114

val city: String = person("city") // "Boston"

115

116

// Access by field key

117

val nameValue = person.get("name") // "Alice"

118

119

// This would fail at compile time:

120

// val invalid = person("salary") // Error: field not found

121

```

122

123

### Record Updates

124

125

```scala { .api }

126

/**

127

* Update existing field or add new field

128

*/

129

def updated[V, F <: Field[V]](f: F, v: V)(implicit updater: Updater[L, F, V]): updater.Out

130

131

/**

132

* Add or update field (operator syntax)

133

*/

134

def +[V, F <: Field[V]](fv: (F, V))(implicit updater: Updater[L, F, V]): updater.Out

135

```

136

137

**Usage Examples:**

138

139

```scala

140

import shapeless._

141

import record._

142

143

val person = ("name" ->> "Bob") :: ("age" ->> 30) :: HNil

144

145

// Update existing field

146

val olderPerson = person.updated("age", 31)

147

// Result: ("name" ->> "Bob") :: ("age" ->> 31) :: HNil

148

149

// Add new field

150

val personWithCity = person + ("city" ->> "Seattle")

151

// Result: ("name" ->> "Bob") :: ("age" ->> 30) :: ("city" ->> "Seattle") :: HNil

152

153

// Update with operator syntax

154

val updatedPerson = person + ("age" ->> 35) + ("active" ->> true)

155

// Result: ("name" ->> "Bob") :: ("age" ->> 35) :: ("active" ->> true) :: HNil

156

```

157

158

### Field Removal

159

160

```scala { .api }

161

/**

162

* Remove field, returning both the value and remaining record

163

*/

164

def remove[F <: FieldAux](f: F)(implicit remove: Remove[FieldEntry[F], L]): (F#valueType, remove.Out)

165

166

/**

167

* Remove field, returning only the remaining record

168

*/

169

def -[F <: FieldAux](f: F)(implicit remove: Remove[FieldEntry[F], L]): remove.Out

170

```

171

172

**Usage Examples:**

173

174

```scala

175

import shapeless._

176

import record._

177

178

val person = ("name" ->> "Charlie") :: ("age" ->> 28) :: ("city" ->> "Portland") :: HNil

179

180

// Remove field and get both value and remaining record

181

val (removedAge, personWithoutAge) = person.remove("age")

182

// removedAge: Int = 28

183

// personWithoutAge: ("name" ->> "Charlie") :: ("city" ->> "Portland") :: HNil

184

185

// Remove field, keep only remaining record

186

val personNoCity = person - "city"

187

// Result: ("name" ->> "Charlie") :: ("age" ->> 28) :: HNil

188

```

189

190

## Type Classes

191

192

### Updater Type Class

193

194

```scala { .api }

195

/**

196

* Supports record update and extension operations

197

*/

198

trait Updater[L <: HList, F <: FieldAux, V] {

199

type Out <: HList

200

def apply(l: L, f: F, v: V): Out

201

}

202

```

203

204

The `Updater` type class handles both updating existing fields and adding new fields to records.

205

206

## Advanced Record Operations

207

208

### Record Merging

209

210

```scala

211

import shapeless._

212

import record._

213

214

val person = ("name" ->> "David") :: ("age" ->> 35) :: HNil

215

val address = ("street" ->> "123 Main St") :: ("city" ->> "Denver") :: HNil

216

217

// Merge records using HList concatenation

218

val fullRecord = person ++ address

219

// Result: ("name" ->> "David") :: ("age" ->> 35) :: ("street" ->> "123 Main St") :: ("city" ->> "Denver") :: HNil

220

221

// Access merged fields

222

val street: String = fullRecord("street") // "123 Main St"

223

val fullName: String = fullRecord("name") // "David"

224

```

225

226

### Record Transformation

227

228

```scala

229

import shapeless._

230

import record._

231

232

val employee = ("name" ->> "Eve") :: ("salary" ->> 50000) :: ("department" ->> "Engineering") :: HNil

233

234

// Transform record using polymorphic functions

235

object upperCase extends Poly1 {

236

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

237

implicit def caseInt = at[Int](identity)

238

}

239

240

// This would require more complex type machinery in practice

241

// val uppercased = employee.map(upperCase) // Not directly supported

242

243

// Instead, use individual field updates

244

val normalized = employee.updated("name", employee("name").toUpperCase)

245

.updated("department", employee("department").toUpperCase)

246

// Result: ("name" ->> "EVE") :: ("salary" ->> 50000) :: ("department" ->> "ENGINEERING") :: HNil

247

```

248

249

### Record Validation

250

251

```scala

252

import shapeless._

253

import record._

254

255

// Type-safe record validation

256

def validatePerson[L <: HList]

257

(person: L)

258

(implicit

259

hasName: Selector[L, FieldEntry[Field[String]]],

260

hasAge: Selector[L, FieldEntry[Field[Int]]]): Boolean = {

261

262

val name = person.get("name")

263

val age = person.get("age")

264

265

name.nonEmpty && age >= 0 && age <= 120

266

}

267

268

val validPerson = ("name" ->> "Frank") :: ("age" ->> 42) :: HNil

269

val isValid = validatePerson(validPerson) // true

270

271

// This would fail at compile time - missing required fields:

272

// val invalidPerson = ("nickname" ->> "Frankie") :: HNil

273

// validatePerson(invalidPerson) // Error: can't find required fields

274

```

275

276

### Nested Records

277

278

```scala

279

import shapeless._

280

import record._

281

282

// Create nested record structures

283

val address = ("street" ->> "456 Oak Ave") :: ("city" ->> "Austin") :: ("zip" ->> "78701") :: HNil

284

val person = ("name" ->> "Grace") :: ("age" ->> 29) :: ("address" ->> address) :: HNil

285

286

// Access nested fields

287

val nestedAddress = person("address")

288

val street: String = nestedAddress("street") // "456 Oak Ave"

289

290

// Update nested records

291

val newAddress = address.updated("street", "789 Pine St")

292

val personWithNewAddress = person.updated("address", newAddress)

293

```

294

295

### Record to Case Class Conversion

296

297

While not directly supported by the record API, records can be converted to case classes using generic programming:

298

299

```scala

300

import shapeless._

301

import record._

302

303

case class Person(name: String, age: Int, city: String)

304

305

val record = ("name" ->> "Henry") :: ("age" ->> 33) :: ("city" ->> "Miami") :: HNil

306

307

// Conversion would require additional machinery (Generic, LabelledGeneric)

308

// This is typically done through shapeless's automatic derivation mechanisms

309

```

310

311

## Field Key Strategies

312

313

### String-based Keys

314

315

```scala

316

import shapeless._

317

import record._

318

319

// Most common approach - string literals as keys

320

val config = ("host" ->> "localhost") :: ("port" ->> 8080) :: ("ssl" ->> false) :: HNil

321

322

val host: String = config("host")

323

val port: Int = config("port")

324

```

325

326

### Symbol-based Keys

327

328

```scala

329

import shapeless._

330

import record._

331

332

// Using symbols as field keys

333

val data = ('timestamp ->> System.currentTimeMillis) ::

334

('level ->> "INFO") ::

335

('message ->> "System started") :: HNil

336

337

val timestamp: Long = data('timestamp)

338

val level: String = data('level)

339

```

340

341

### Typed Keys

342

343

```scala

344

import shapeless._

345

import record._

346

347

// Define custom typed field keys

348

object Keys {

349

case object Username extends Field[String]

350

case object UserId extends Field[Int]

351

case object IsActive extends Field[Boolean]

352

}

353

354

import Keys._

355

356

val user = (Username ->> "admin") :: (UserId ->> 1001) :: (IsActive ->> true) :: HNil

357

358

val username: String = user(Username)

359

val userId: Int = user(UserId)

360

val active: Boolean = user(IsActive)

361

```

362

363

Records in shapeless provide a powerful abstraction for working with labeled data structures while maintaining compile-time safety and type inference. They bridge the gap between the flexibility of dynamic field access and the safety of static typing.