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

records-fields.mddocs/

0

# Records and Named Field Access

1

2

Records in Shapeless provide structured data access using field names rather than positions, offering a more user-friendly interface for working with case classes and other product types. Records enable dynamic field access while maintaining compile-time type safety.

3

4

## Capabilities

5

6

### Core Record Types

7

8

Fundamental types for creating and working with named field access.

9

10

```scala { .api }

11

// Field with singleton-typed key K and value type V

12

type FieldType[K, +V] = V @@ KeyTag[K, V]

13

14

// Phantom tag for associating keys with values

15

trait KeyTag[K, +V]

16

17

// Type alias for records (HList of FieldType)

18

type Record = HList // where elements are FieldType[_, _]

19

```

20

21

### Field Construction

22

23

Building blocks for creating fields with typed keys.

24

25

```scala { .api }

26

// Field builder for key K

27

object field[K] extends FieldOf[K]

28

29

trait FieldOf[K] {

30

def apply[V](value: V): FieldType[K, V]

31

}

32

33

// Field builder that applies values to create fields

34

trait FieldBuilder[K] {

35

def apply[V](v: V): FieldType[K, V]

36

}

37

38

// Usage:

39

val nameField = field['name]("Alice")

40

val ageField = field['age](30)

41

val record = nameField :: ageField :: HNil

42

```

43

44

### Dynamic Record Construction

45

46

Dynamic construction of records using method names as field keys.

47

48

```scala { .api }

49

object Record {

50

// Dynamic record construction via method calls

51

def applyDynamic(method: String)(args: Any*): HList

52

53

// Named argument construction

54

def applyDynamicNamed(method: String)(args: (String, Any)*): HList

55

56

// Dynamic field type selection

57

def selectDynamic(field: String): Any

58

}

59

60

// Dynamic argument mapping for records

61

trait RecordArgs[R <: HList] {

62

def apply(rec: R): Any

63

}

64

65

// Inverse mapping from records to named arguments

66

trait FromRecordArgs[A] {

67

type Out <: HList

68

def apply(args: A): Out

69

}

70

```

71

72

### Record Field Operations

73

74

Core operations for accessing and manipulating record fields.

75

76

```scala { .api }

77

// Select field with key K from record L

78

trait Selector[L <: HList, K] {

79

type Out

80

def apply(l: L): Out

81

}

82

83

// Update field F in record L

84

trait Updater[L <: HList, F] {

85

type Out <: HList

86

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

87

}

88

89

// Remove field with key K from record L

90

trait Remover[L <: HList, K] {

91

type Out <: HList

92

def apply(l: L): (Out, FieldType[K, _])

93

}

94

95

// Usage:

96

val record = field['name]("Bob") :: field['age](25) :: HNil

97

val name = record.get('name) // "Bob"

98

val updated = record.updated('age, 26)

99

val (remaining, removed) = record.remove('name)

100

```

101

102

### Record Structure Operations

103

104

Operations for extracting and manipulating record structure.

105

106

```scala { .api }

107

// Extract all keys from record L as HList

108

trait Keys[L <: HList] {

109

type Out <: HList

110

def apply(l: L): Out

111

}

112

113

// Extract all values from record L as HList

114

trait Values[L <: HList] {

115

type Out <: HList

116

def apply(l: L): Out

117

}

118

119

// Merge two records L and M

120

trait Merger[L <: HList, M <: HList] {

121

type Out <: HList

122

def apply(l: L, m: M): Out

123

}

124

125

// Rename fields according to key mapping

126

trait Renamer[L <: HList, KS <: HList] {

127

type Out <: HList

128

def apply(l: L): Out

129

}

130

131

// Example:

132

val keys = record.keys // 'name :: 'age :: HNil

133

val values = record.values // "Bob" :: 25 :: HNil

134

```

135

136

### Labelled Infrastructure

137

138

Infrastructure for working with labeled types and symbolic labels.

139

140

```scala { .api }

141

// Extract symbolic labels from case class/sealed trait

142

trait DefaultSymbolicLabelling[T] {

143

type Out

144

def apply(): Out

145

}

146

147

// Base trait for polymorphic functions preserving field keys

148

trait FieldPoly extends Poly

149

150

// Type class witnessing field value types

151

trait FieldOf[V] {

152

type Out <: HList

153

def apply(): Out

154

}

155

156

// Create field type from key and value

157

def fieldType[K, V](key: K, value: V): FieldType[K, V]

158

```

159

160

## Usage Examples

161

162

### Basic Record Operations

163

164

```scala

165

import shapeless._, record._, syntax.singleton._

166

167

// Create record using field syntax

168

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

169

170

// Access fields by name

171

val name: String = person('name)

172

val age: Int = person('age)

173

val active: Boolean = person('active)

174

175

// Update fields

176

val older = person.updated('age, 31)

177

val renamed = person.updated('name, "Alicia")

178

179

// Remove fields

180

val (withoutAge, ageField) = person.remove('age)

181

// withoutAge: ('name ->> String) :: ('active ->> Boolean) :: HNil

182

```

183

184

### Dynamic Record Construction

185

186

```scala

187

// Using dynamic syntax

188

val dynamicRecord = Record.empty

189

.add('firstName, "John")

190

.add('lastName, "Doe")

191

.add('email, "john.doe@example.com")

192

193

// Access dynamically

194

val firstName = dynamicRecord.get('firstName) // "John"

195

val email = dynamicRecord.get('email) // "john.doe@example.com"

196

```

197

198

### Record Merging and Transformation

199

200

```scala

201

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

202

val contact = ('phone ->> "555-1234") :: ('email ->> "test@example.com") :: HNil

203

204

// Merge records

205

val combined = person ++ address ++ contact

206

207

// Extract keys and values

208

val allKeys = combined.keys

209

val allValues = combined.values

210

211

// Transform values while preserving keys

212

object addPrefix extends FieldPoly {

213

implicit def stringCase[K] = atField[K, String](s => s"prefix_$s")

214

implicit def intCase[K] = atField[K, Int](i => i * 100)

215

}

216

217

val transformed = combined.map(addPrefix)

218

```

219

220

### Integration with Case Classes

221

222

```scala

223

case class Employee(name: String, id: Int, department: String, salary: Double)

224

225

val emp = Employee("Sarah", 12345, "Engineering", 95000.0)

226

227

// Convert to labeled generic (preserves field names)

228

val lgen = LabelledGeneric[Employee]

229

val empRecord = lgen.to(emp)

230

// Result has field names as singleton types

231

232

// Access by field name

233

val empName = empRecord.get('name) // "Sarah"

234

val empId = empRecord.get('id) // 12345

235

val empDept = empRecord.get('department) // "Engineering"

236

237

// Update and convert back

238

val promoted = empRecord.updated('salary, 105000.0)

239

val updatedEmp = lgen.from(promoted)

240

// Employee("Sarah", 12345, "Engineering", 105000.0)

241

```

242

243

### Record Validation and Constraints

244

245

```scala

246

// Define validation rules

247

object validateRecord extends FieldPoly {

248

implicit def nameValidation = atField['name, String] { name =>

249

require(name.nonEmpty, "Name cannot be empty")

250

name

251

}

252

253

implicit def ageValidation = atField['age, Int] { age =>

254

require(age >= 0, "Age must be non-negative")

255

age

256

}

257

}

258

259

// Apply validations

260

try {

261

val validatedPerson = person.map(validateRecord)

262

println("Validation passed")

263

} catch {

264

case e: IllegalArgumentException => println(s"Validation failed: ${e.getMessage}")

265

}

266

```

267

268

### Record Renaming

269

270

```scala

271

// Define key mapping for renaming

272

val keyMapping = ('name ->> 'fullName) :: ('age ->> 'yearsOld) :: HNil

273

274

// Rename fields

275

val renamedPerson = person.renameFields(keyMapping)

276

// Result: ('fullName ->> "Alice") :: ('yearsOld ->> 30) :: ('active ->> true) :: HNil

277

278

val fullName = renamedPerson('fullName) // "Alice"

279

val yearsOld = renamedPerson('yearsOld) // 30

280

```

281

282

### Working with Optional Fields

283

284

```scala

285

val optionalRecord =

286

('name ->> Some("Bob")): FieldType['name, Option[String]] ::

287

('age ->> None): FieldType['age, Option[Int]] ::

288

('email ->> Some("bob@example.com")): FieldType['email, Option[String]] ::

289

HNil

290

291

// Extract present values

292

object extractPresent extends FieldPoly {

293

implicit def optionCase[K, V] = atField[K, Option[V]] {

294

case Some(v) => v

295

case None => throw new NoSuchElementException(s"Field not present")

296

}

297

}

298

299

// Get only present values (will throw for None)

300

val name = optionalRecord.get('name).get // "Bob"

301

val email = optionalRecord.get('email).get // "bob@example.com"

302

```