Shapeless

Me

What is shapeless?

Shapeless is an exploration of generic (aka polytypic) programming in Scala

What is shapeless?

Miles Sabin

Voodoo master

Unboxed Types

'Tag' a type with additional type information

without 'boxing'

Easier if I just show you...

Unboxed Types - Example 1


def template(text : String) = "<div>" + text + "</div>"
template("<script>alert('Scala is too hard')</script>")
          

How do we know 'string' has been escaped?

Unboxed Types - Example 1


sealed trait SafeString

def escape(s : String) : String @@ SafeString = tag[SafeString](s.replace("<", "&lt;"))

def template(text : String @@ SafeString) = "<div>" + text + "</div>"
          

Unboxed Types - Example 1


// template("<script>alert('Scala is too hard')</script>")

template(escape("<script>alert('Scala is too hard')</script>"))
          

Unboxed Types - Example 1

If we decompile:


public String escape(String s) {
    return s.replace("<", "&lt;");
}
          

Unboxed Types - Warning


def withUnboxed(i: Int @@ Foo): ...

private final void withUnboxed(int i)
{
    BoxesRunTime.boxToInteger(i)
}
          

Unboxed Types - Example 2

Scalaz use it do distinguish multiple implementations of a type class

Unboxed Types - Example 2


implicit val plus = new Monoid[Int] {
  def zero = 0
  def append(a : Int, b : Int) = a + b
}
implicit val multiply = new Monoid[Int] {
  def zero = 1
  def append(a : Int, b : Int) = a * b
}
          

Unboxed Types - Example 2


2 |+| 3
> 5
          

Unboxed Types - Example 2


def Multiplication[A](a: A): A @@ Multiplication = tag[Multiplication](a)

Multiplication(2) |+| Multiplication(3)
> 6
          

Unboxed Types

We also use it at Atlassian:


trait Identify[T] {
  type Id = java.lang.Long @@ T
}
object Application extends Identify[Application]
object Plugin extends Identify[Plugin]

def findApplication(id: Application.Id) = dao.find(id)
          

Part 2

"The mother of all Scala HList's"

List


val list = "a" :: "b" :: "c" :: Nil
> List[String]
list.map(_.length)
> List[Int]
          

List


val list = "string" :: 1 :: true :: Nil
> List[Any]
list.map {
  case x : Int => x.toString
  case s : String => s.length
  case _ => 0
}
> List[Any]
          

Heterogeneous List

aka HList


import shapeless._
"string" :: 1 :: true :: HNil
> String :: Int :: Boolean :: HNil
          

Heterogeneous List


val hlist = "string" :: 1 :: true :: HNil
// def singleton[A](a: A) = Set(a)
hlist.map(singleton)
> Set[String] :: Set[Int] :: Set[Boolean] :: HNil
          

Heterogeneous List

Let's try:


val hlist = "string" :: 1 :: true :: HNil
hlist.map(a => Set(a))
> error: missing parameter type
> error: could not find implicit value for parameter mapper: Mapper[=>, String :: Int :: Boolean :: HNil]
          

Detour

Polymorphic Functions

Don't we have them already?


def monomorphic(s : String) : Int = s.length
monomorphic("foo") 

def polymorphic[T](l : List[T]) : Int = l.length
polymorphic(List(1, 2, 3))
polymorphic(List("foo", "bar", "baz"))
          

Polymorphic Functions


def singleton[A](a: A) = Set(a)
List("a", "b").map(singleton)
> List[Set[String]]

// set scalacOptions += "-Xprint:typer"
List("a", "b").map((a: String) => singleton[String](a))

// This is known as eta-expansion.
          

Polymorphic Functions

Remember


trait Function1[-T, +R] {
  def apply(t : T) : R
}
          

Polymorphic Functions

What we want (sort of):


trait PolyFunction1 {
  def apply[T, R](t : T) : R
}
// This doesn't compile
object singleton extends PolyFunction1 {
   def apply[T, R <: Set[T]](s : T): R = Set(s)
}
          

Polymorphic Functions

Let's use higher kinded types!


trait PolyFunctionTake1[F[_]] {
  def apply[T](t : T) : F[T]
}
          

Polymorphic Functions


object singleton extends PolyFunctionTake1[Set] {
  def apply[T](t : T) : Set[T] = Set(t)
}
          

Polymorphic Functions


val hlist = List("a", "b") :: List(1, 2) :: List[Boolean]() :: HNil
hlist.map(headOption)
> Option[String] :: Option[Int] :: Option[Boolean] :: HNil =
  Some("a")      :: Some(1)      :: None           :: HNil
          

Polymorphic Functions

But, doesn't work for headOption.


object headOption extends PolyFunctionTake1[Option] {
  // Ahh how do we know t is a List?!?
  def apply[T](l : T) : Option[T] = l.headOption
}
          

Polymorphic Functions

Take 2


trait PolyFunction1[F[_], G[_]] {
  def apply[T](f : F[T]) : G[T]
}
          

Polymorphic Functions


object headOption extends PolyFunction1[List, Option] {
  def apply[T](l : List[T]) : Option[T] = l.headOption
}
          

Polymorphic Functions

What about singleton?


type Id[T] = T
object singleton extends PolyFunction1[Id, Set] {
  def apply[T](t : T) : Set[T] = Set(t)
}
          

Sugar!

Polymorphic Functions


trait ~>[F[_], G[_]] {
  def apply[T](f : F[T]) : G[T]
}
          

HList


object singleton extends (Id ~> Set) {
  def apply[T](t : T) = Set(t)
}
object headOption extends (List ~> Option) {
  def apply[T](l : List[T]) : Option[T] = l.headOption
}
          

HList

What about a more complex example?


object complex extends (Id ~> Id) {
  def apply[T](t : T) : Int = t match {
    case x : Int => x.toString
    case s : String  => s.toUpperCase
    case _ => false
  }
}
          

Hmm, not very type safe

May as well use List[Any]

HList

Presto!


object complex extends Poly1 {
   implicit def caseInt = at[Int](_.toString)
   implicit def caseString = at[String](_.length)
 }
          

HList


val list = "a" :: 1 :: "b" :: HNil
list.map(complex)
> Int :: String :: Int :: HNil = 1 :: "1" : 1 :: HNil
          

HList

Under the hood


val y = ("a" :: 1 :: "b" :: HNil).map(complex)(
Mapper.mapper         [complex.type,  String :: Int :: String :: HNil,    Int :: String :: Int :: HNil](
MapperAux.hlistMapper1[complex.type,  String, Int, Int :: String :: HNil, String :: Int :: HNil]       (complex.caseString,
MapperAux.hlistMapper1[complex.type,  Int, String, String :: HNil,        Int :: HNil]                 (complex.caseInt,
MapperAux.hlistMapper1[complex.type,  String, Int, HNil,                  HNil]                        (complex.caseString,
MapperAux.hnilMapper1 [complex.type]
)))))
          

HList

Cool stuff they can do

HList

Tuples


val t1 = (23, "foo", 2.0, true)

l1 = t1.hlisted
> 23 :: "foo" :: 2.0 :: true :: HNil

> l1.tupled
> (23, "foo", 2.0, true)
          

HList

Case Classes


case class Foo(i : Int, s : String, d : Double)
implicit def fooIso = HListIso(Foo.apply _, Foo.unapply _)

case class Bar(s : String, d : Double)
implicit def barIso = HListIso(Bar.apply _, Bar.unapply _)

val list = toHList(Foo(0, "a", 1.2))
> Int :: String :: Double :: HNil

val shorterList = list.tail
> String :: Double :: HNil

fromHList(shorterList)
> Bar("a", 1.2)
          

HList

Free Monoids


Foo(13, "foo", 1.0) |+| Foo(23, "bar", 3.0)
> Foo(36, "foobar", 4.0)
          

HList

Zipper


val l = 1 :: "foo" :: 3.0 :: HNil

l.toZipper.right.put("wibble", 45).toHList
> 1 :: ("wibble", 45) :: 3.0 :: HNil

l.toZipper.right.delete.toHList
> 1 :: 3.0 :: HNil

l.toZipper.last.left.insert("bar").toHList
> 1 :: "foo" :: "bar" :: 3.0 :: HNil
          

HList

Example


class CalculatorResource {
  @Path("add/{a}/to/{b}")
  def add(@Param("a") a: Int, @Param("b") b: Int) = ...
}
def addUrl(a: Int, b: Int) = "add/" + a + "/to/" + b
          

HList

Example - Bigtop Routes


object Calculator extends Site {
  val add =
    ("add" :/: IntArg :/: "to" :/: IntArg :/: end) >>
      (a: Int, b: Int) => ...
}
Calculator.add.url(1, 2) // ==> bigtop.core.Url("/add/1/to/2")
          

HList

Example 2 - SQL


List<Object[]> results = session.query("select a, b from Foo");
// Unsafe results
// Even with Hibernate/HQL - Constraints are the best we can do?!?
          

HList

Example 2 - Theoretical


trait Query {
  def query[T <: HList](a : Select[T]): Results[T]
}
val select = query.select[String]("a").select[Int]("b")
val result: Results[String :: Int :: HNil] = session.query(select)
          

HList

Example 3 - Tuples in Scoobi


  def mkCaseWireFormat[T, A1: WireFormat, A2: WireFormat]
    (apply: (A1, A2) => T, unapply: T => Option[(A1, A2)]): WireFormat[T]
    = new WireFormat[T] {

    override def toWire(obj: T, out: DataOutput) {
      val v: Product2[A1, A2] = unapply(obj).get
      implicitly[WireFormat[A1]].toWire(v._1, out)
      implicitly[WireFormat[A2]].toWire(v._2, out)
    }

    override def fromWire(in: DataInput): T = {
      val a1: A1 = implicitly[WireFormat[A1]].fromWire(in)
      val a2: A2 = implicitly[WireFormat[A2]].fromWire(in)
      apply(a1, a2)
    }
  }
          

HList

Example 3 - Tuples in Scoobi


def mkCaseWireFormat[T, A1: WireFormat, A2: WireFormat, A3: WireFormat]
  (apply: (A1, A2, A3) => T, unapply: T => Option[(A1, A2, A3)])
  = new WireFormat[T] {
  override def toWire(obj: T, out: DataOutput) {
    val v: Product3[A1, A2, A3] = unapply(obj).get
    implicitly[WireFormat[A1]].toWire(v._1, out)
    implicitly[WireFormat[A2]].toWire(v._2, out)
    implicitly[WireFormat[A3]].toWire(v._3, out)
  }

  override def fromWire(in: DataInput): T = {
    val a1: A1 = implicitly[WireFormat[A1]].fromWire(in)
    val a2: A2 = implicitly[WireFormat[A2]].fromWire(in)
    val a3: A3 = implicitly[WireFormat[A3]].fromWire(in)
    apply(a1, a2, a3)
  }
}
          

HList

Example 3 - HLists in Scoobi


object WireAux {
  implicit def hnil = new WireAux[HNil] {
    def toWire(l: HNil, s: DataInput) {}
    def fromWire(t: DataInput) = HNil
  }
  implicit def hlist[InH, InT <: HList](implicit wf: WireFormat[InH], mt: WireAux[InT]) = new WireAux[InH :: InT] {
    def toWire(l: InH :: InT, out: DataInput) { wf.toWire(l.head, out); mt.toWire(l.tail, out) }
    def fromWire(in: DataInput): InH :: InT = wf.fromWire(in) :: mt.fromWire(in)
  }
}

def mkHListWireFormat[T, L <: HList](iso: HListIso[T, L])(implicit aux: WireAux[L]) = new WireFormat[T] {
  def toWire(obj: T, out: DataInput) = aux.toWire(iso.toHList(obj), out)
  def fromWire(in: DataInput) = iso.fromHList(aux.fromWire(in))
}

implicit val iso = HListIso(Something.apply _, Something.unapply _)
val wf = mkHListWireFormat(iso)
          

Theres more...

  • Heterogenous maps
  • Extensible records
  • Sized types
  • Type safe cast
  • Scrap your Boilerplate
  • Newtype

Just for fun - Fibonacci


import Fibonacci._
typed[_0](fibonacci(_0))
typed[_1](fibonacci(_1))
typed[_1](fibonacci(_2))
typed[_2](fibonacci(_3))
typed[_3](fibonacci(_4))
typed[_5](fibonacci(_5))
typed[_8](fibonacci(_6))
typed[_13](fibonacci(_7))
          

References

http://www.chuusai.com/blog/ http://etorreborre.blogspot.com.au/2011/11/practical-uses-for-unboxed-tagged-types.html http://timperrett.com/2012/06/15/unboxed-new-types-within-scalaz7/

Thank you