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

poly-typelevel.mddocs/

0

# Polymorphic Functions and Type-Level Programming

1

2

Shapeless provides sophisticated type-level programming capabilities including polymorphic functions that work across different types, natural transformations, type-level arithmetic with natural numbers, and advanced type-level logic for compile-time computations.

3

4

## Capabilities

5

6

### Polymorphic Functions (Poly)

7

8

Functions that can operate on different types while preserving type information and enabling type-dependent behavior.

9

10

```scala { .api }

11

// Base trait for polymorphic functions

12

trait Poly {

13

// Apply to HList of arguments with case resolution

14

def apply[L <: HList](args: L)(implicit cse: Case.Aux[this.type, L, Out]): Out

15

}

16

17

// Arity-specific polymorphic function traits

18

trait Poly0 extends Poly {

19

def apply()(implicit cse: Case0[this.type]): cse.Result

20

}

21

22

trait Poly1 extends Poly {

23

def apply[A](a: A)(implicit cse: Case1[A, this.type]): cse.Result

24

}

25

26

trait Poly2 extends Poly {

27

def apply[A, B](a: A, b: B)(implicit cse: Case2[A, B, this.type]): cse.Result

28

}

29

30

// Continue for higher arities...

31

```

32

33

### Case Definitions

34

35

Type-specific implementations for polymorphic functions with dependent result types.

36

37

```scala { .api }

38

// General case with dependent result type

39

trait Case[P, L <: HList] {

40

type Result

41

def apply(args: L): Result

42

}

43

44

// Convenience aliases for common arities

45

type Case0[P] = Case[P, HNil]

46

type Case1[A, P] = Case[P, A :: HNil]

47

type Case2[A, B, P] = Case[P, A :: B :: HNil]

48

49

// Case for transducer-style operations

50

trait CaseTransducer[P, A] {

51

type Out

52

def apply(a: A): Out

53

}

54

55

// Usage pattern:

56

object myPoly extends Poly1 {

57

implicit def intCase = at[Int](i => i * 2)

58

implicit def stringCase = at[String](s => s.toUpperCase)

59

implicit def boolCase = at[Boolean](b => !b)

60

}

61

```

62

63

### Polymorphic Function Combinators

64

65

Operations for composing and transforming polymorphic functions.

66

67

```scala { .api }

68

// Function composition (F ∘ G)

69

trait Compose[F, G] extends Poly1 {

70

def apply[A](a: A)(implicit

71

gCase: G.Case1[A],

72

fCase: F.Case1[gCase.Result]

73

): fCase.Result

74

}

75

76

// Rotate function arguments left by N positions

77

trait RotateLeft[P, N <: Nat] extends Poly

78

79

// Rotate function arguments right by N positions

80

trait RotateRight[P, N <: Nat] extends Poly

81

82

// Usage:

83

val composed = Compose(f, g) // Applies g then f

84

val rotated = RotateLeft(myPoly, Nat._2)

85

```

86

87

### Built-in Polymorphic Functions

88

89

Common polymorphic functions provided by Shapeless.

90

91

```scala { .api }

92

// Identity function - returns input unchanged

93

object identity extends Poly1 {

94

implicit def default[A] = at[A](a => a)

95

}

96

97

// Constant function returning fixed value

98

def const[T](value: T): Poly1 = new Poly1 {

99

implicit def default[A] = at[A](_ => value)

100

}

101

102

// Convert values to singleton types

103

object singleton extends Poly1 {

104

implicit def default[A] = at[A](a => a: a.type)

105

}

106

107

// Convert to string representation

108

object stringify extends Poly1 {

109

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

110

}

111

```

112

113

### Natural Transformations

114

115

Polymorphic functions between type constructors (functors).

116

117

```scala { .api }

118

// Natural transformation between unary type constructors

119

trait ~>[F[_], G[_]] {

120

def apply[A](fa: F[A]): G[A]

121

}

122

123

// Natural transformation between binary type constructors

124

trait ~~>[F[_, _], G[_, _]] {

125

def apply[A, B](fab: F[A, B]): G[A, B]

126

}

127

128

// Example transformations:

129

val optionToList = new (Option ~> List) {

130

def apply[A](opt: Option[A]): List[A] = opt.toList

131

}

132

133

val listToVector = new (List ~> Vector) {

134

def apply[A](list: List[A]): Vector[A] = list.toVector

135

}

136

```

137

138

### Natural Numbers (Nat)

139

140

Compile-time natural numbers for type-level arithmetic and indexing.

141

142

```scala { .api }

143

// Base sealed trait with associated type member

144

sealed trait Nat {

145

type N <: Nat

146

}

147

148

// Successor type representing S(n) = n+1

149

sealed trait Succ[P <: Nat] extends Nat {

150

type N = Succ[P]

151

}

152

153

// Zero type and value object

154

sealed trait _0 extends Nat {

155

type N = _0

156

}

157

case object _0 extends _0

158

159

// Nat companion object

160

object Nat {

161

// Convert runtime Int to compile-time Nat

162

def apply(i: Int): Nat = macro NatMacros.materialize

163

164

// Convert compile-time Nat to runtime Int

165

def toInt[N <: Nat](implicit toInt: ToInt[N]): Int = toInt()

166

167

// Type aliases for common numbers

168

type _1 = Succ[_0]

169

type _2 = Succ[_1]

170

type _3 = Succ[_2]

171

// ... continues to _22 and beyond

172

}

173

```

174

175

### Nat Operations

176

177

Type-level arithmetic operations on natural numbers.

178

179

```scala { .api }

180

// Type-level addition A + B

181

trait Sum[A <: Nat, B <: Nat] {

182

type Out <: Nat

183

}

184

185

// Type-level subtraction A - B (when A >= B)

186

trait Diff[A <: Nat, B <: Nat] {

187

type Out <: Nat

188

}

189

190

// Type-level multiplication A * B

191

trait Prod[A <: Nat, B <: Nat] {

192

type Out <: Nat

193

}

194

195

// Type-level division A / B

196

trait Div[A <: Nat, B <: Nat] {

197

type Out <: Nat

198

}

199

200

// Type-level modulo A % B

201

trait Mod[A <: Nat, B <: Nat] {

202

type Out <: Nat

203

}

204

205

// Comparison operations

206

trait LT[A <: Nat, B <: Nat] // A < B

207

trait LTEq[A <: Nat, B <: Nat] // A <= B

208

trait GT[A <: Nat, B <: Nat] // A > B

209

trait GTEq[A <: Nat, B <: Nat] // A >= B

210

211

// Min and Max operations

212

trait Min[A <: Nat, B <: Nat] { type Out <: Nat }

213

trait Max[A <: Nat, B <: Nat] { type Out <: Nat }

214

```

215

216

### Finite Types (Fin)

217

218

Finite types with exactly N inhabitants for type-safe indexing.

219

220

```scala { .api }

221

// Finite type with exactly N inhabitants (0, 1, ..., N-1)

222

sealed trait Fin[N <: Nat] {

223

def toInt: Int

224

}

225

226

object Fin {

227

// Create Fin value from Int with bounds checking

228

def apply[N <: Nat](i: Int)(implicit toInt: ToInt[N]): Option[Fin[N]]

229

230

// Unsafe creation (no bounds checking)

231

def unsafeFrom[N <: Nat](i: Int): Fin[N]

232

233

// Convert to Int

234

def toInt[N <: Nat](fin: Fin[N]): Int

235

}

236

237

// Usage for type-safe array indexing

238

def safeGet[A, N <: Nat](arr: Array[A], index: Fin[N]): Option[A] = {

239

if (index.toInt < arr.length) Some(arr(index.toInt)) else None

240

}

241

```

242

243

### Singleton Types and Witnesses

244

245

Working with singleton types and compile-time constants.

246

247

```scala { .api }

248

// Witness that T is a singleton type

249

trait Witness[T] {

250

type Out = T

251

val value: T

252

}

253

254

object Witness {

255

// Type alias for witness with specific singleton type

256

type Aux[T] = Witness[T] { type Out = T }

257

258

// Create witness for singleton type

259

def apply[T](implicit w: Witness[T]): Witness.Aux[T] = w

260

}

261

262

// Witness with additional type class constraint

263

trait WitnessWith[TC[_]] {

264

type Out

265

val value: Out

266

val typeClass: TC[Out]

267

}

268

269

// Usage:

270

val intWitness = Witness(42) // Witness for 42.type

271

val stringWitness = Witness("test") // Witness for "test".type

272

```

273

274

### Type-Level Logic

275

276

Boolean logic and reasoning at the type level.

277

278

```scala { .api }

279

// Type-level boolean values

280

sealed trait Bool

281

sealed trait True extends Bool

282

sealed trait False extends Bool

283

284

// Logical operations

285

trait Not[B <: Bool] { type Out <: Bool }

286

trait And[A <: Bool, B <: Bool] { type Out <: Bool }

287

trait Or[A <: Bool, B <: Bool] { type Out <: Bool }

288

289

// Type-level if-then-else

290

trait If[C <: Bool, T, F] { type Out }

291

292

// Evidence for type relationships

293

trait =:=[A, B] // Type equality

294

trait <:<[A, B] // Subtype relationship

295

trait =:!=[A, B] // Type inequality

296

trait <:!<[A, B] // Subtype exclusion

297

```

298

299

## Usage Examples

300

301

### Basic Polymorphic Functions

302

303

```scala

304

import shapeless._

305

306

// Define polymorphic function

307

object size extends Poly1 {

308

implicit def stringCase = at[String](_.length)

309

implicit def listCase[A] = at[List[A]](_.size)

310

implicit def optionCase[A] = at[Option[A]](_.size)

311

implicit def intCase = at[Int](_ => 1) // Size of Int is always 1

312

}

313

314

// Apply to different types

315

val stringSize = size("hello") // 5

316

val listSize = size(List(1, 2, 3)) // 3

317

val optionSize = size(Some(42)) // 1

318

val intSize = size(123) // 1

319

320

// Use with HList

321

val mixed = "test" :: List(1, 2) :: Some(true) :: HNil

322

val sizes = mixed.map(size) // 4 :: 2 :: 1 :: HNil

323

```

324

325

### Type-Level Arithmetic

326

327

```scala

328

// Compile-time arithmetic

329

type Five = Succ[Succ[Succ[Succ[Succ[_0]]]]] // or Nat._5

330

type Three = Succ[Succ[Succ[_0]]] // or Nat._3

331

332

// Type-level operations

333

type Eight = Sum[Five, Three] // 5 + 3 = 8

334

type Two = Diff[Five, Three] // 5 - 3 = 2

335

type Fifteen = Prod[Five, Three] // 5 * 3 = 15

336

337

// Runtime conversion

338

val eight: Int = Nat.toInt[Eight] // 8

339

val two: Int = Nat.toInt[Two] // 2

340

val fifteen: Int = Nat.toInt[Fifteen] // 15

341

342

// Comparison evidence

343

def compareNats[A <: Nat, B <: Nat](implicit lt: LT[A, B]): String =

344

"A is less than B"

345

346

val comparison = compareNats[Three, Five] // "A is less than B"

347

```

348

349

### Natural Transformations

350

351

```scala

352

// Define container transformations

353

val optionToEither = new (Option ~> ({ type λ[A] = Either[String, A] })#λ) {

354

def apply[A](opt: Option[A]): Either[String, A] =

355

opt.toRight("None value")

356

}

357

358

val listToNel = new (List ~> ({ type λ[A] = Option[A] })#λ) {

359

def apply[A](list: List[A]): Option[A] = list.headOption

360

}

361

362

// Apply transformations

363

val someValue: Option[Int] = Some(42)

364

val eitherValue = optionToEither(someValue) // Right(42)

365

366

val listValue: List[String] = List("a", "b", "c")

367

val headValue = listToNel(listValue) // Some("a")

368

```

369

370

### Advanced Polymorphic Function Composition

371

372

```scala

373

object double extends Poly1 {

374

implicit def intCase = at[Int](_ * 2)

375

implicit def stringCase = at[String](s => s + s)

376

}

377

378

object increment extends Poly1 {

379

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

380

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

381

}

382

383

// Compose functions

384

val doubleThincrement = Compose(increment, double)

385

386

val result1 = doubleThincrement(5) // double(5) = 10, increment(10) = 11

387

val result2 = doubleThincrement("hi") // double("hi") = "hihi", increment("hihi") = "hihi!"

388

```

389

390

### Type-Safe Indexing with Fin

391

392

```scala

393

// Type-safe array access

394

def typeSafeArray[A](elements: A*): TypeSafeArray[A] =

395

new TypeSafeArray(elements.toArray)

396

397

class TypeSafeArray[A](private val arr: Array[A]) {

398

def length: Nat = Nat(arr.length)

399

400

def apply[N <: Nat](index: Fin[N])(implicit

401

ev: LT[N, length.type]

402

): A = arr(index.toInt)

403

404

def get[N <: Nat](index: Fin[N]): Option[A] = {

405

val i = index.toInt

406

if (i < arr.length) Some(arr(i)) else None

407

}

408

}

409

410

// Usage

411

val array = typeSafeArray("a", "b", "c", "d")

412

413

// Type-safe access (compile-time bounds checking)

414

val index0 = Fin[Nat._4](0).get // Some(Fin[_4])

415

val index3 = Fin[Nat._4](3).get // Some(Fin[_4])

416

val index4 = Fin[Nat._4](4) // None (out of bounds)

417

418

val elem0 = array.get(index0.get) // Some("a")

419

val elem3 = array.get(index3.get) // Some("d")

420

```

421

422

### Singleton Type Programming

423

424

```scala

425

// Work with singleton types

426

val fortyTwo = Witness(42)

427

val hello = Witness("hello")

428

429

// Type-level string manipulation

430

type HelloWorld = "hello" + " " + "world" // Requires recent Scala versions

431

432

// Singleton-typed collections

433

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

434

// Type: 42.type :: "test".type :: true.type :: HNil

435

436

// Extract singleton values at compile time

437

def getValue[S](implicit w: Witness.Aux[S]): S = w.value

438

439

val constantFortyTwo: 42 = getValue[42] // 42

440

val constantHello: "hello" = getValue["hello"] // "hello"

441

```

442

443

### Complex Type-Level Computations

444

445

```scala

446

// Type-level list operations

447

trait Length[L <: HList] { type Out <: Nat }

448

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

449

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

450

451

// Compute length at type level

452

type MyList = String :: Int :: Boolean :: HNil

453

type MyLength = Length[MyList] // Nat._3

454

455

// Split operations

456

type FirstTwo = Take[MyList, Nat._2] // String :: Int :: HNil

457

type LastOne = Drop[MyList, Nat._2] // Boolean :: HNil

458

459

// Type-level predicates

460

trait All[L <: HList, P[_]] { type Out <: Bool }

461

trait Any[L <: HList, P[_]] { type Out <: Bool }

462

463

// Check if all elements are numeric

464

trait IsNumeric[T] { type Out <: Bool }

465

type AllNumeric = All[Int :: Double :: Float :: HNil, IsNumeric] // True

466

```