CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-chuusai--shapeless-2-11

An exploration of generic (aka polytypic) programming in Scala derived from implementing scrap your boilerplate and higher rank polymorphism patterns

Pending
Overview
Eval results
Files

nat.mddocs/

Type-level Natural Numbers

Type-level natural numbers in shapeless enable compile-time arithmetic and computations. They allow you to express constraints, perform calculations, and verify properties at the type level, providing additional safety and enabling advanced generic programming patterns.

Core Types

Base Natural Number Type

/**
 * Base trait for type-level natural numbers
 */
trait Nat

Zero

/**
 * Type-level zero with implicit value
 */
class _0 extends Nat
implicit val _0: _0 = new _0

Successor

/**
 * Successor type - represents N+1 for a natural number N
 */
case class Succ[P <: Nat]() extends Nat

Generated Natural Numbers

Shapeless provides pre-defined type aliases for common natural numbers:

type _0 = shapeless._0
type _1 = Succ[_0]  
type _2 = Succ[_1]
type _3 = Succ[_2]
// ... continues up to _22

Usage Examples:

import shapeless._

// Using type-level numbers
val zero: _0 = _0
val one: _1 = Succ[_0]()  
val two: _2 = Succ[_1]()
val three: _3 = Succ[_2]()

// Type-level constraints
def takeN[L <: HList, N <: Nat](hlist: L, n: N)
  (implicit take: Take[L, N]): take.Out = hlist.take(n)

val hlist = 1 :: "hello" :: true :: 3.14 :: HNil
val first2 = takeN(hlist, _2)  // 1 :: "hello" :: HNil

Runtime Conversion

ToInt Type Class

/**
 * Converts type-level Nat to runtime Int
 */
trait ToInt[N <: Nat] {
  def apply(): Int
}

Utility Functions

/**
 * Convert type-level natural number to Int
 */
def toInt[N <: Nat](implicit toIntN: ToInt[N]): Int
def toInt[N <: Nat](n: N)(implicit toIntN: ToInt[N]): Int

Usage Examples:

import shapeless._

val five: _5 = Succ[Succ[Succ[Succ[Succ[_0]]]]]()

// Convert to runtime Int
val runtimeFive: Int = toInt[_5]        // 5
val alsoFive: Int = toInt(five)         // 5  

// Use in contexts requiring Int
def repeat[N <: Nat](s: String, n: N)(implicit toIntN: ToInt[N]): String = 
  s * toInt(n)

val repeated = repeat("hi", _3)  // "hihihi"

Arithmetic Operations

Addition

/**
 * Type-level addition A + B
 */
trait Sum[A <: Nat, B <: Nat] {
  type Out <: Nat
}

Usage Examples:

import shapeless._

// Type-level arithmetic in function signatures
def addLengths[L1 <: HList, L2 <: HList, N1 <: Nat, N2 <: Nat]
  (h1: L1, h2: L2)
  (implicit 
   len1: Length.Aux[L1, N1],
   len2: Length.Aux[L2, N2],  
   sum: Sum[N1, N2]): sum.Out = {
  // Implementation would use type class instances
  null.asInstanceOf[sum.Out]
}

val h1 = 1 :: 2 :: HNil           // Length = _2
val h2 = "a" :: "b" :: "c" :: HNil // Length = _3  
val totalLength = addLengths(h1, h2)  // Type: _5 (at type level)

Subtraction

/**
 * Type-level subtraction A - B  
 */
trait Diff[A <: Nat, B <: Nat] {
  type Out <: Nat
}

Usage Examples:

import shapeless._

// Use in take/drop operations
def takeAndCount[L <: HList, N <: Nat, M <: Nat]
  (hlist: L, take: N, total: M)
  (implicit 
   takeOp: Take.Aux[L, N, _],
   diff: Diff[M, N]): diff.Out = {
  // Would return remaining count after taking N elements
  null.asInstanceOf[diff.Out]
}

Multiplication

/**
 * Type-level multiplication A * B
 */
trait Prod[A <: Nat, B <: Nat] {
  type Out <: Nat
}

Division and Modulo

/**
 * Type-level division A / B
 */
trait Div[A <: Nat, B <: Nat] {
  type Out <: Nat
}

/**
 * Type-level modulo A % B  
 */
trait Mod[A <: Nat, B <: Nat] {
  type Out <: Nat
}

Comparison Operations

Less Than

/**
 * Witnesses that A < B
 */
trait LT[A <: Nat, B <: Nat]

/**
 * Convenient type alias for LT
 */
type <[A <: Nat, B <: Nat] = LT[A, B]

Usage Examples:

import shapeless._

// Safe head operation that requires non-empty evidence
def safeHead[L <: HList, N <: Nat]
  (hlist: L)
  (implicit 
   len: Length.Aux[L, N],
   nonEmpty: _0 < N): hlist.H = hlist.head

val hlist = 1 :: "hello" :: HNil
val head = safeHead(hlist)  // Works: length is _2, and _0 < _2

// This would fail at compile time:
// val emptyHead = safeHead(HNil)  // Error: can't prove _0 < _0

Less Than or Equal

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

/**
 * Convenient type alias for LTEq
 */
type <=[A <: Nat, B <: Nat] = LTEq[A, B]

Predecessor

/**
 * Predecessor operation - computes N-1
 */
trait Pred[N <: Nat] {
  type Out <: Nat
}

Usage Examples:

import shapeless._

// Safe tail that maintains length relationship
def safeTail[L <: HList, N <: Nat]
  (hlist: L)
  (implicit 
   len: Length.Aux[L, N],
   pred: Pred[N],
   nonEmpty: _0 < N): Sized[HList, pred.Out] = {
  // Would return tail with length N-1
  ???
}

Integration with HList Operations

Type-level natural numbers integrate deeply with HList operations:

Indexed Access

import shapeless._

val hlist = "a" :: "b" :: "c" :: "d" :: HNil

// Type-safe indexed access
val first: String = hlist(0)   // "a" (using _0)  
val second: String = hlist(1)  // "b" (using _1)
val third: String = hlist(2)   // "c" (using _2)

// This would fail at compile time:
// val outOfBounds = hlist(5)  // Error: can't prove 5 < length

Take and Drop Operations

import shapeless._

val hlist = 1 :: 2 :: 3 :: 4 :: 5 :: HNil

// Type-safe slicing with compile-time verification
val first3 = hlist.take(_3)    // 1 :: 2 :: 3 :: HNil (type: Take[..., _3])
val last2 = hlist.drop(_3)     // 4 :: 5 :: HNil (type: Drop[..., _3])

// Split maintains type-level relationship
val (prefix, suffix) = hlist.split(_2)  
// prefix: 1 :: 2 :: HNil
// suffix: 3 :: 4 :: 5 :: HNil

Length Verification

import shapeless._

// Function that only accepts HLists of specific length
def processPair[T](pair: T :: T :: HNil): String = 
  s"Processing pair: ${pair.head} and ${pair.tail.head}"

val validPair = 1 :: 2 :: HNil
val result = processPair(validPair)  // Works

// This would fail at compile time:
// val invalidPair = 1 :: 2 :: 3 :: HNil  
// processPair(invalidPair)  // Error: doesn't match T :: T :: HNil

Advanced Usage Patterns

Sized Collection Integration

import shapeless._
import syntax.sized._

// Create sized collections with type-level length
val sizedList = List(1, 2, 3, 4, 5).sized(_5)  // Option[Sized[List[Int], _5]]

sizedList.map { sized =>
  val head = sized.head     // Safe: _0 < _5 is provable
  val tail = sized.tail     // Type: Sized[List[Int], _4]
  val first3 = sized.take(_3)  // Type: Sized[List[Int], _3]
}

Type-level Constraints

import shapeless._

// Function that requires minimum length
def processAtLeast3[L <: HList, N <: Nat]
  (hlist: L)
  (implicit 
   len: Length.Aux[L, N],
   minLength: _3 <= N): String = s"Processing ${toInt[N]} elements"

val longList = 1 :: 2 :: 3 :: 4 :: HNil  
val result = processAtLeast3(longList)  // Works: _4 >= _3

val shortList = 1 :: 2 :: HNil
// processAtLeast3(shortList)  // Error: can't prove _3 <= _2

Compile-time Arithmetic

import shapeless._

// Type-level function that doubles a number
trait Double[N <: Nat] {
  type Out <: Nat  
}

implicit val doubleZero: Double[_0] { type Out = _0 } = 
  new Double[_0] { type Out = _0 }

implicit def doubleSucc[N <: Nat, D <: Nat]
  (implicit double: Double.Aux[N, D], sum: Sum[D, _2]): Double[Succ[N]] { type Out = sum.Out } =
  new Double[Succ[N]] { type Out = sum.Out }

// Usage in type constraints
def repeatDouble[N <: Nat](s: String)
  (implicit double: Double[N], toInt: ToInt[double.Out]): String = 
  s * toInt[double.Out]

val doubled = repeatDouble[_3]("hi")  // Would repeat "hi" 6 times

Type-level natural numbers provide the foundation for compile-time verification and enable shapeless to catch errors at compile time rather than runtime, making generic programming both safer and more expressive.

Install with Tessl CLI

npx tessl i tessl/maven-com-chuusai--shapeless-2-11

docs

conversions.md

generic.md

hlist.md

hmap.md

index.md

lift.md

nat.md

poly.md

records.md

sized.md

sybclass.md

typeable.md

typeoperators.md

tile.json