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

conversions.mddocs/

Type-safe Conversions

Shapeless provides type-safe bidirectional conversions between various Scala data structures including tuples, functions, and HLists. These conversions maintain type safety and enable seamless interoperability between different representations of the same data.

Tuple ↔ HList Conversions

HLister Type Class

/**
 * Converts Products (tuples) to HLists
 */
trait HLister[T <: Product] {
  type Out <: HList
  def apply(t: T): Out
}

Tupler Type Class

/**
 * Converts HLists to tuples (when possible)
 */
trait Tupler[L <: HList] {
  type Out <: Product
  def apply(l: L): Out
}

Tuples Object

/**
 * Provides enhanced operations for tuple conversions
 */
object Tuples {
  trait TupleOps[L <: HList] {
    def hlisted: L
  }
  
  implicit def tupleOps[T <: Product](t: T)(implicit hlister: HLister[T]): TupleOps[hlister.Out]
  
  object hlisted extends Poly1  // Converts Tuples to HLists
  object tupled extends Poly1   // Converts HLists to Tuples
}

Usage Examples:

import shapeless._
import syntax.std.tuple._

// Convert tuple to HList
val tuple = (42, "hello", true)
val hlist = tuple.hlisted  // Int :: String :: Boolean :: HNil

// Convert HList to tuple  
val backToTuple = hlist.tupled  // (Int, String, Boolean) = (42, "hello", true)

// Polymorphic conversion
import Tuples._

val pairs = ((1, "a"), (2, "b"), (3, "c"))
val hlistPairs = (pairs._1.hlisted, pairs._2.hlisted, pairs._3.hlisted)
// (1 :: "a" :: HNil, 2 :: "b" :: HNil, 3 :: "c" :: HNil)

Product Arity

/**
 * Witnesses the arity of a Product type
 */
trait ProductArity[P <: Product] {
  type N <: Nat  // Arity as natural number
}

Usage Examples:

import shapeless._

// Get arity information at type level
def showArity[P <: Product, N <: Nat]
  (p: P)
  (implicit arity: ProductArity.Aux[P, N], toInt: ToInt[N]): Int = toInt()

val pair = (1, "hello")
val triple = (1, "hello", true)

val pairArity = showArity(pair)    // 2
val tripleArity = showArity(triple) // 3

Function Conversions

Function to HList Function

/**
 * Converts ordinary functions to HList functions
 */
trait FnHLister[F] {
  type Out
  def apply(f: F): Out
}

HList Function to Function

/**
 * Converts HList functions to ordinary functions
 */
trait FnUnHLister[F] {
  type Out
  def apply(f: F): Out
}

Functions Object

/**
 * Provides enhanced operations for function conversions
 */
object Functions {
  trait FnHListOps[HLFn] {
    def hlisted: HLFn
  }
  
  trait FnUnHListOps[F] {
    def unhlisted: F
  }
}

Usage Examples:

import shapeless._
import syntax.std.function._

// Convert regular function to HList function
val add: (Int, Int) => Int = _ + _
val hlistAdd = add.hlisted  // (Int :: Int :: HNil) => Int

// Apply HList function
val args = 5 :: 3 :: HNil
val result = hlistAdd(args)  // 8

// Convert back to regular function
val regularAdd = hlistAdd.unhlisted  // (Int, Int) => Int
val result2 = regularAdd(7, 2)  // 9

// More complex example
val processData: (String, Int, Boolean) => String = 
  (name, age, active) => s"$name is $age years old and ${if (active) "active" else "inactive"}"

val hlistProcessor = processData.hlisted
val data = "Alice" :: 25 :: true :: HNil
val processed = hlistProcessor(data)  // "Alice is 25 years old and active"

Function Arity Support

Shapeless provides function conversion support for functions of arity 0 through 22:

import shapeless._
import syntax.std.function._

// Nullary function
val getValue: () => String = () => "constant"
val hlistGetValue = getValue.hlisted  // HNil => String
val value = hlistGetValue(HNil)  // "constant"

// Unary function  
val double: Int => Int = _ * 2
val hlistDouble = double.hlisted  // (Int :: HNil) => Int
val doubled = hlistDouble(5 :: HNil)  // 10

// Higher arity functions
val combine5: (Int, String, Boolean, Double, Char) => String = 
  (i, s, b, d, c) => s"$i-$s-$b-$d-$c"
  
val hlistCombine5 = combine5.hlisted
val args5 = 42 :: "test" :: true :: 3.14 :: 'x' :: HNil
val combined = hlistCombine5(args5)  // "42-test-true-3.14-x"

Traversable Conversions

FromTraversable Type Class

/**
 * Type-safe conversion from Traversables to HLists
 */
trait FromTraversable[Out <: HList] {
  def apply(l: GenTraversable[_]): Option[Out]
}

Traversables Object

/**
 * Provides enhanced operations for traversable conversions
 */
object Traversables {
  trait TraversableOps {
    def toHList[L <: HList](implicit fl: FromTraversable[L]): Option[L]
  }
}

Usage Examples:

import shapeless._
import syntax.std.traversable._

// Convert List to HList with specific type structure
val mixedList: List[Any] = List(42, "hello", true)
val hlistOpt: Option[Int :: String :: Boolean :: HNil] = mixedList.toHList

hlistOpt match {
  case Some(hlist) => 
    val i: Int = hlist.head           // 42
    val s: String = hlist.tail.head   // "hello" 
    val b: Boolean = hlist.tail.tail.head  // true
  case None => 
    println("Conversion failed - type mismatch")
}

// Safe conversion - returns None on type mismatch
val wrongTypes: List[Any] = List("not", "an", "int")
val failedConversion: Option[Int :: String :: Boolean :: HNil] = wrongTypes.toHList
// failedConversion: None

// Convert collections of uniform type
val numbers = List(1, 2, 3)  
val numberHList: Option[Int :: Int :: Int :: HNil] = numbers.toHList

Generic Conversions

Automatic Conversions

Shapeless provides automatic conversions between equivalent representations:

import shapeless._

// Case class to HList (requires Generic)
case class Person(name: String, age: Int, active: Boolean)

val person = Person("Bob", 30, true)
// Conversion to HList happens through Generic (covered in generic.md)

// Automatic tuple/HList interop in operations
val tuple = (1, "hello", true)
val hlist = 42 :: "world" :: false :: HNil

// Can zip tuple with HList (after conversion)
val tupleAsHList = tuple.hlisted
val zipped = tupleAsHList.zip(hlist)
// (1, 42) :: ("hello", "world") :: (true, false) :: HNil

Bidirectional Operations

import shapeless._
import syntax.std.tuple._

// Complex conversion pipeline
val data = ((1, "a"), (2, "b"), (3, "c"))

// Convert tuple of tuples to HList of HLists
val step1 = data.hlisted  // (1, "a") :: (2, "b") :: (3, "c") :: HNil
val step2 = step1.map(hlisted)  // Would require proper Poly1 setup

// Practical example with functions
val processor: ((String, Int)) => String = { case (name, age) => s"$name:$age" }
val hlistProcessor = processor.hlisted  // ((String :: Int :: HNil)) => String

val inputs = ("Alice" :: 25 :: HNil) :: ("Bob" :: 30 :: HNil) :: HNil
// Process each input (would need proper mapping setup)

Advanced Conversion Patterns

Conditional Conversions

import shapeless._

// Convert only when types match exactly
def safeConvert[T <: Product, L <: HList]
  (product: T)
  (implicit hlister: HLister.Aux[T, L]): L = hlister(product)

val tuple2 = (42, "hello")
val tuple3 = (42, "hello", true)

val hlist2 = safeConvert(tuple2)  // Int :: String :: HNil
val hlist3 = safeConvert(tuple3)  // Int :: String :: Boolean :: HNil

Type-preserving Operations

import shapeless._
import syntax.std.tuple._
import syntax.std.function._

// Round-trip conversion maintains types
def roundTrip[T <: Product, L <: HList]
  (t: T)
  (implicit 
   hlister: HLister.Aux[T, L],
   tupler: Tupler.Aux[L, T]): T = {
  
  val hlist = t.hlisted
  // Could perform HList operations here
  hlist.tupled
}

val original = (1, "test", true)
val afterRoundTrip = roundTrip(original)
// afterRoundTrip has same type and value as original

Conversion Validation

import shapeless._

// Validate conversions at compile time
def validateConversion[T <: Product, L <: HList, T2 <: Product]
  (t: T)
  (implicit 
   hlister: HLister.Aux[T, L],
   tupler: Tupler.Aux[L, T2],
   ev: T =:= T2): T2 = {
  
  t.hlisted.tupled
}

// This ensures the round-trip conversion preserves exact types
val validated = validateConversion((1, "hello"))
// Type is exactly (Int, String)

Integration with Collections

import shapeless._
import syntax.std.tuple._
import syntax.std.traversable._

// Convert collection of tuples to collection of HLists
val tupleList = List((1, "a"), (2, "b"), (3, "c"))
val hlistList = tupleList.map(_.hlisted)
// List[Int :: String :: HNil]

// Process and convert back
val processedHLists = hlistList.map(_.reverse)  
val backToTuples = processedHLists.map(_.tupled)
// List[("a", 1), ("b", 2), ("c", 3)]

Type-safe conversions in shapeless enable seamless interoperability between different data representations while maintaining compile-time safety and type inference. This allows you to choose the most appropriate representation for each operation while avoiding runtime type errors.

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