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

poly.mddocs/

0

# Polymorphic Functions

1

2

Polymorphic functions in shapeless allow you to define functions that can have different behavior for different types while maintaining a unified interface. This enables type-safe generic programming where the same function name can perform type-specific operations.

3

4

## Core Abstractions

5

6

### Base Poly Trait

7

8

```scala { .api }

9

/**

10

* Base trait for polymorphic functions

11

*/

12

trait Poly extends Product with Serializable {

13

type Case0[T] = Case0Aux[this.type, T]

14

type Case1[T] = Case1Aux[this.type, T]

15

type Case2[T, U] = Case2Aux[this.type, T, U]

16

17

def apply[T](implicit c: Case0[T]): T

18

def apply[T](t: T)(implicit c: Case1[T]): c.R

19

def apply[T, U](t: T, u: U)(implicit c: Case2[T, U]): c.R

20

}

21

```

22

23

### Poly0 - Polymorphic Values

24

25

```scala { .api }

26

/**

27

* Polymorphic values (nullary functions) - different values for different types

28

*/

29

trait Poly0 extends Poly {

30

def at[T](v: T) = new Case0[T] { val value = v }

31

}

32

```

33

34

**Usage Examples:**

35

36

```scala

37

import shapeless._

38

39

object zero extends Poly0 {

40

implicit val zeroInt = at[Int](0)

41

implicit val zeroDouble = at[Double](0.0)

42

implicit val zeroString = at[String]("")

43

implicit def zeroList[T] = at[List[T]](Nil)

44

}

45

46

val intZero: Int = zero[Int] // 0

47

val stringZero: String = zero[String] // ""

48

val listZero: List[String] = zero[List[String]] // Nil

49

```

50

51

### Poly1 - Unary Polymorphic Functions

52

53

```scala { .api }

54

/**

55

* Polymorphic unary functions - different implementations for different input types

56

*/

57

trait Poly1 extends Poly {

58

def at[T] = new Case1Builder[T]

59

60

class Case1Builder[T] {

61

def apply[R0](f: T => R0) = new Case1[T] {

62

type R = R0

63

val value = f

64

}

65

}

66

}

67

```

68

69

**Usage Examples:**

70

71

```scala

72

import shapeless._

73

74

object size extends Poly1 {

75

implicit def caseInt = at[Int](identity)

76

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

77

implicit def caseList[T] = at[List[T]](_.length)

78

implicit def caseOption[T] = at[Option[T]](_.fold(0)(_ => 1))

79

}

80

81

val intSize: Int = size(42) // 42

82

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

83

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

84

val optionSize: Int = size(Some("x")) // 1

85

86

// Use with HList.map

87

val hlist = 42 :: "world" :: List(1, 2) :: HNil

88

val sizes = hlist.map(size) // 42 :: 5 :: 2 :: HNil

89

```

90

91

### Poly2 - Binary Polymorphic Functions

92

93

```scala { .api }

94

/**

95

* Polymorphic binary functions - different implementations for different input type combinations

96

*/

97

trait Poly2 extends Poly {

98

def at[T, U] = new Case2Builder[T, U]

99

100

class Case2Builder[T, U] {

101

def apply[R0](f: (T, U) => R0) = new Case2[T, U] {

102

type R = R0

103

val value = f

104

}

105

}

106

}

107

```

108

109

**Usage Examples:**

110

111

```scala

112

import shapeless._

113

114

object plus extends Poly2 {

115

implicit val caseInt = at[Int, Int](_ + _)

116

implicit val caseDouble = at[Double, Double](_ + _)

117

implicit val caseString = at[String, String](_ + _)

118

implicit def caseList[T] = at[List[T], List[T]](_ ::: _)

119

}

120

121

val sumInt = plus(5, 3) // 8

122

val sumString = plus("hello", "world") // "helloworld"

123

val sumList = plus(List(1, 2), List(3, 4)) // List(1, 2, 3, 4)

124

125

// Use with HList operations

126

val left = 1 :: "a" :: List(1) :: HNil

127

val right = 2 :: "b" :: List(2) :: HNil

128

val result = left.zip(right).map(plus) // 3 :: "ab" :: List(1, 2) :: HNil

129

```

130

131

## Natural Transformations

132

133

### Natural Transformation (~>)

134

135

```scala { .api }

136

/**

137

* Natural transformation from type constructor F to G

138

*/

139

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

140

def apply[T](f: F[T]): G[T]

141

}

142

```

143

144

**Usage Examples:**

145

146

```scala

147

import shapeless._

148

149

object optionToList extends (Option ~> List) {

150

def apply[T](o: Option[T]): List[T] = o.toList

151

}

152

153

object listToOption extends (List ~> Option) {

154

def apply[T](l: List[T]): Option[T] = l.headOption

155

}

156

157

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

158

val asList: List[Int] = optionToList(someValue) // List(42)

159

val backToOption: Option[Int] = listToOption(asList) // Some(42)

160

161

// Use with HList of different container types

162

val containers = Some(1) :: List("a", "b") :: Some(true) :: HNil

163

val allLists = containers.map(optionToList) // List(1) :: List("a", "b") :: List(true) :: HNil

164

```

165

166

### Constant Natural Transformation (~>>)

167

168

```scala { .api }

169

/**

170

* Natural transformation from type constructor F to constant type R

171

*/

172

trait ~>>[F[_], R] extends Pullback1[R] {

173

def apply[T](f: F[T]): R

174

}

175

```

176

177

**Usage Examples:**

178

179

```scala

180

import shapeless._

181

182

object isEmpty extends (Option ~>> Boolean) {

183

def apply[T](o: Option[T]): Boolean = o.isEmpty

184

}

185

186

object size extends (List ~>> Int) {

187

def apply[T](l: List[T]): Int = l.size

188

}

189

190

val checks = Some(42) :: None :: Some("hello") :: HNil

191

val results = checks.map(isEmpty) // false :: true :: false :: HNil

192

```

193

194

## Predefined Polymorphic Functions

195

196

### Identity Functions

197

198

```scala { .api }

199

/**

200

* Identity natural transformation

201

*/

202

object identity extends (Id ~> Id) {

203

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

204

}

205

```

206

207

### Option Operations

208

209

```scala { .api }

210

/**

211

* Wrap values in Option

212

*/

213

object option extends (Id ~> Option) {

214

def apply[T](t: T) = Option(t)

215

}

216

217

/**

218

* Extract values from Option (unsafe)

219

*/

220

object get extends (Option ~> Id) {

221

def apply[T](o: Option[T]) = o.get

222

}

223

224

/**

225

* Check if Option is defined

226

*/

227

object isDefined extends (Option ~>> Boolean) {

228

def apply[T](o: Option[T]) = o.isDefined

229

}

230

```

231

232

### Collection Operations

233

234

```scala { .api }

235

/**

236

* Create singleton Set

237

*/

238

object singleton extends (Id ~> Set) {

239

def apply[T](t: T) = Set(t)

240

}

241

242

/**

243

* Choose element from Set

244

*/

245

object choose extends (Set ~> Option) {

246

def apply[T](s: Set[T]) = s.headOption

247

}

248

249

/**

250

* Create singleton List

251

*/

252

object list extends (Id ~> List) {

253

def apply[T](t: T) = List(t)

254

}

255

256

/**

257

* Get head of List as Option

258

*/

259

object headOption extends (List ~> Option) {

260

def apply[T](l: List[T]) = l.headOption

261

}

262

```

263

264

**Usage Examples:**

265

266

```scala

267

import shapeless._

268

269

val values = 1 :: "hello" :: true :: HNil

270

val options = values.map(option) // Some(1) :: Some("hello") :: Some(true) :: HNil

271

val sets = values.map(singleton) // Set(1) :: Set("hello") :: Set(true) :: HNil

272

val lists = values.map(list) // List(1) :: List("hello") :: List(true) :: HNil

273

val defined = options.map(isDefined) // true :: true :: true :: HNil

274

```

275

276

## Function Lifting

277

278

### Lifting Function1 to Poly1

279

280

```scala { .api }

281

/**

282

* Lifts a Function1 to Poly1 with subtyping support

283

*/

284

class ->[T, R](f: T => R) extends Poly1 {

285

implicit def subT[U <: T] = at[U](f)

286

}

287

```

288

289

**Usage Examples:**

290

291

```scala

292

import shapeless._

293

294

val addOne = new ->[Int, Int](_ + 1)

295

val toString = new ->[Any, String](_.toString)

296

297

val numbers = 1 :: 2 :: 3 :: HNil

298

val incremented = numbers.map(addOne) // 2 :: 3 :: 4 :: HNil

299

300

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

301

val strings = mixed.map(toString) // "42" :: "hello" :: "true" :: HNil

302

```

303

304

### Lifting to HList Range

305

306

```scala { .api }

307

/**

308

* Lifts Function1 to Poly1 over universal domain, results wrapped in HList

309

*/

310

class >->[T, R](f: T => R) extends LowPriorityLiftFunction1 {

311

implicit def subT[U <: T] = at[U](t => f(t) :: HNil)

312

}

313

```

314

315

## Advanced Usage Patterns

316

317

### Composing Polymorphic Functions

318

319

```scala

320

import shapeless._

321

322

object increment extends Poly1 {

323

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

324

implicit def caseDouble = at[Double](_ + 1.0)

325

}

326

327

object stringify extends Poly1 {

328

implicit def caseInt = at[Int](_.toString)

329

implicit def caseDouble = at[Double](_.toString)

330

}

331

332

// Chain polymorphic operations

333

val numbers = 5 :: 2.5 :: 10 :: HNil

334

val incremented = numbers.map(increment) // 6 :: 3.5 :: 11 :: HNil

335

val strings = incremented.map(stringify) // "6" :: "3.5" :: "11" :: HNil

336

```

337

338

### Type-specific Behavior

339

340

```scala

341

import shapeless._

342

343

object processValue extends Poly1 {

344

implicit def caseString = at[String](s => s"Processed string: $s")

345

implicit def caseInt = at[Int](i => s"Processed number: ${i * 2}")

346

implicit def caseBoolean = at[Boolean](b => s"Processed boolean: ${!b}")

347

implicit def caseList[T] = at[List[T]](l => s"Processed list of ${l.size} items")

348

}

349

350

val mixed = "hello" :: 42 :: true :: List(1, 2, 3) :: HNil

351

val processed = mixed.map(processValue)

352

// "Processed string: hello" :: "Processed number: 84" ::

353

// "Processed boolean: false" :: "Processed list of 3 items" :: HNil

354

```

355

356

### Conditional Processing

357

358

```scala

359

import shapeless._

360

361

object safeHead extends Poly1 {

362

implicit def caseList[T] = at[List[T]](_.headOption)

363

implicit def caseOption[T] = at[Option[T]](identity)

364

implicit def caseString = at[String](s => if (s.nonEmpty) Some(s.head) else None)

365

}

366

367

val collections = List(1, 2, 3) :: "" :: "hello" :: Some(42) :: None :: HNil

368

val heads = collections.map(safeHead)

369

// Some(1) :: None :: Some('h') :: Some(42) :: None :: HNil

370

```

371

372

## Integration with HList Operations

373

374

Polymorphic functions integrate seamlessly with HList operations:

375

376

```scala

377

import shapeless._

378

379

// Map over HList elements

380

val data = 1 :: "hello" :: List(1, 2) :: HNil

381

val sizes = data.map(size) // 1 :: 5 :: 2 :: HNil

382

383

// Fold with polymorphic operations

384

val sum = sizes.foldLeft(0)(plus) // 8

385

386

// Filter and transform

387

val mixed = 1 :: 2.5 :: "test" :: 3 :: HNil

388

val numbers = mixed.filter[Int] ++ mixed.filter[Double] // 1 :: 3 :: 2.5 :: HNil

389

val doubled = numbers.map(plus) // Error: need two arguments

390

391

// Zip and apply

392

val left = 1 :: "a" :: List(1) :: HNil

393

val right = 2 :: "b" :: List(2) :: HNil

394

val combined = left.zip(right).map(plus) // 3 :: "ab" :: List(1, 2) :: HNil

395

```

396

397

This polymorphic function system forms the backbone of shapeless's type-safe generic programming capabilities, allowing you to write functions that adapt their behavior based on the types they encounter while maintaining compile-time safety.