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

hlist-collections.mddocs/

0

# HLists and Heterogeneous Collections

1

2

Heterogeneous Lists (HLists) are the fundamental data structure in Shapeless, allowing you to create lists that contain elements of different types while preserving complete type information at compile time. They provide the foundation for most other Shapeless operations.

3

4

## Capabilities

5

6

### Core HList Types

7

8

The basic building blocks for creating and working with heterogeneous lists.

9

10

```scala { .api }

11

// Base trait for all heterogeneous lists

12

sealed trait HList

13

14

// Cons cell - non-empty HList with head H and tail T

15

final case class ::[+H, +T <: HList](head: H, tail: T) extends HList

16

17

// Empty HList

18

sealed trait HNil extends HList

19

case object HNil extends HNil

20

21

// Type aliases for convenience

22

type HNil = shapeless.HNil

23

type ::[H, T <: HList] = shapeless.::[H, T]

24

```

25

26

### HList Construction

27

28

Multiple ways to create HLists from various data sources.

29

30

```scala { .api }

31

object HList {

32

// Create empty HList

33

def apply(): HNil.type

34

35

// Create single-element HList

36

def apply[T](t: T): T :: HNil

37

38

// Create HList from Product (tuple, case class)

39

def apply[P <: Product, L <: HList](p: P)(implicit gen: Generic.Aux[P, L]): L

40

41

// Fill HList with n repeated elements

42

def fill[A](n: Nat)(elem: A)(implicit fill: Fill[n.N, A]): fill.Out

43

44

// Fill 2D HList structure

45

def fill[A](n1: Nat, n2: Nat)(elem: A)(implicit fill: Fill[(n1.N, n2.N), A]): fill.Out

46

47

// Fill with polymorphic function - returns FillWithOps

48

def fillWith[L <: HList]: FillWithOps[L]

49

}

50

51

// Helper class for fillWith operation

52

final class FillWithOps[L <: HList] {

53

def apply[F <: Poly](f: F)(implicit fillWith: FillWith[F, L]): L

54

}

55

56

// Cons operator - infix syntax for construction

57

def ::[H, T <: HList](head: H, tail: T): H :: T

58

59

// Construction examples:

60

val empty: HNil = HNil

61

val single: String :: HNil = "hello" :: HNil

62

val mixed: String :: Int :: Boolean :: HNil = "test" :: 42 :: true :: HNil

63

```

64

65

### Element Access and Selection

66

67

Type-safe access to HList elements by position or type.

68

69

```scala { .api }

70

// Access by compile-time position

71

trait At[L <: HList, N <: Nat] {

72

type Out

73

def apply(l: L): Out

74

}

75

76

// Select element by type (must be unique)

77

trait Selector[L <: HList, U] {

78

def apply(l: L): U

79

}

80

81

// Check if HList is non-empty and get head/tail

82

trait IsHCons[L <: HList] {

83

type H

84

type T <: HList

85

def head(l: L): H

86

def tail(l: L): T

87

}

88

89

// Usage examples:

90

val hlist = "test" :: 42 :: true :: HNil

91

val head: String = hlist.head

92

val tail = hlist.tail

93

val byType: Int = hlist.select[Int]

94

val byPos = hlist.at(Nat._1) // Gets element at position 1 (42)

95

```

96

97

### Structure Operations

98

99

Operations that modify the structure of HLists.

100

101

```scala { .api }

102

// Get length as compile-time natural number

103

trait Length[L <: HList] {

104

type Out <: Nat

105

def apply(l: L): Out

106

}

107

108

// Prepend one HList to another

109

trait Prepend[P <: HList, S <: HList] {

110

type Out <: HList

111

def apply(prefix: P, suffix: S): Out

112

}

113

114

// Reverse HList

115

trait Reverse[L <: HList] {

116

type Out <: HList

117

def apply(l: L): Out

118

}

119

120

// Take first N elements

121

trait Take[L <: HList, N <: Nat] {

122

type Out <: HList

123

def apply(l: L): Out

124

}

125

126

// Drop first N elements

127

trait Drop[L <: HList, N <: Nat] {

128

type Out <: HList

129

def apply(l: L): Out

130

}

131

132

// Split at position N

133

trait Split[L <: HList, N <: Nat] {

134

type Prefix <: HList

135

type Suffix <: HList

136

def apply(l: L): (Prefix, Suffix)

137

}

138

```

139

140

### Functional Operations

141

142

Map, fold, and other functional operations over HLists.

143

144

```scala { .api }

145

// Map polymorphic function over HList

146

trait Mapper[L <: HList, F] {

147

type Out <: HList

148

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

149

}

150

151

// Flat map operation

152

trait FlatMapper[L <: HList, F] {

153

type Out <: HList

154

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

155

}

156

157

// Fold with polymorphic function

158

trait Folder[L <: HList, V, F] {

159

type Out

160

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

161

}

162

163

// Left fold with binary function

164

trait LeftFolder[L <: HList, V, F] {

165

type Out

166

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

167

}

168

169

// Right fold with binary function

170

trait RightFolder[L <: HList, V, F] {

171

type Out

172

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

173

}

174

175

// Usage example:

176

object addOne extends Poly1 {

177

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

178

implicit def caseString = at[String](_ + "!")

179

}

180

181

val mapped = hlist.map(addOne) // "test!" :: 43 :: true :: HNil

182

```

183

184

### Zipper Operations

185

186

Zip and unzip operations for working with pairs and tuples.

187

188

```scala { .api }

189

// Zip HList of pairs into pair of HLists

190

trait Zip[L <: HList] {

191

type Out

192

def apply(l: L): Out

193

}

194

195

// Unzip pair of HLists into HList of pairs

196

trait Unzip[L] {

197

type Out <: HList

198

def apply(l: L): Out

199

}

200

201

// Apply HList of functions to HList of arguments

202

trait ZipApply[L <: HList, A <: HList] {

203

type Out <: HList

204

def apply(fl: L, al: A): Out

205

}

206

207

// Example:

208

val pairs = ("a", 1) :: ("b", 2) :: HNil

209

val (strings, ints) = pairs.unzip // ("a" :: "b" :: HNil, 1 :: 2 :: HNil)

210

```

211

212

### Type Constraints

213

214

Apply constraints to all elements of an HList.

215

216

```scala { .api }

217

// All elements satisfy constraint C

218

trait Constraint[L <: HList, C[_]] {

219

type Out <: HList

220

def apply(l: L): Out

221

}

222

223

// Unary type constructor constraint

224

trait UnaryTCConstraint[L <: HList, C[_]] {

225

type Out <: HList

226

def apply(l: L, f: C ~> Id): Out

227

}

228

229

// Collect evidence for constraint

230

trait BasisConstraint[L <: HList, M[_]] {

231

type Out <: HList

232

def apply(l: L): Out

233

}

234

235

// Example - ensure all elements are Numeric

236

def sumAll[L <: HList](l: L)(implicit

237

constraint: Constraint[L, Numeric],

238

folder: LeftFolder[L, Int, +.type]

239

): folder.Out = l.foldLeft(0)(+)

240

```

241

242

### Conversion Operations

243

244

Convert HLists to and from other data structures.

245

246

```scala { .api }

247

// Convert HList to corresponding tuple

248

trait Tupler[L <: HList] {

249

type Out

250

def apply(l: L): Out

251

}

252

253

// Convert to generic representation

254

trait Generic[T] {

255

type Repr

256

def to(t: T): Repr

257

def from(repr: Repr): T

258

}

259

260

// Examples:

261

val hlist = "test" :: 42 :: true :: HNil

262

val tuple: (String, Int, Boolean) = hlist.tupled

263

val backToHList = tuple.productElements // Extension method from shapeless

264

```

265

266

### Sized Collections

267

268

Collections with compile-time known size information.

269

270

```scala { .api }

271

// Wrapper for collections with size information

272

final case class Sized[Repr, L <: Nat](unsized: Repr)

273

274

// Create sized collection

275

def sized[CC[_], T, N <: Nat](cc: CC[T])(implicit

276

s: AdditiveCollection[CC[T], T, CC[T]],

277

l: Length.Aux[CC[T], N]

278

): Sized[CC[T], N]

279

280

// Operations preserve size information

281

trait SizedMap[Repr, L <: Nat, A, B] {

282

type Out

283

def apply(s: Sized[Repr, L], f: A => B): Sized[Out, L]

284

}

285

286

// Usage:

287

val sizedList = Sized[List[Int], Nat._3](List(1, 2, 3))

288

val mapped = sizedList.map(_ * 2) // Still Sized[List[Int], Nat._3]

289

```

290

291

## Usage Examples

292

293

### Basic HList Operations

294

295

```scala

296

import shapeless._

297

298

// Create HList

299

val data = "Alice" :: 30 :: true :: HNil

300

301

// Access elements

302

val name: String = data.head

303

val rest = data.tail

304

val age: Int = data.select[Int]

305

val status: Boolean = data.at(Nat._2)

306

307

// Modify structure

308

val extended = false :: data // Boolean :: String :: Int :: Boolean :: HNil

309

val shortened = data.take(Nat._2) // String :: Int :: HNil

310

val reversed = data.reverse // Boolean :: Int :: String :: HNil

311

```

312

313

### Polymorphic Function Mapping

314

315

```scala

316

object stringify extends Poly1 {

317

implicit def default[T] = at[T](_.toString)

318

implicit def caseString = at[String](s => s"'$s'")

319

}

320

321

val mixed = "test" :: 42 :: true :: HNil

322

val strings = mixed.map(stringify) // "'test'" :: "42" :: "true" :: HNil

323

```

324

325

### Converting Between Products and HLists

326

327

```scala

328

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

329

330

val person = Person("Bob", 25, false)

331

val gen = Generic[Person]

332

333

// Convert to HList

334

val hlist = gen.to(person) // String :: Int :: Boolean :: HNil

335

336

// Modify using HList operations

337

val older = hlist.updatedBy[Int](_ + 1)

338

val modified = gen.from(older) // Person("Bob", 26, false)

339

```