Shapeless is an exploration of generic (aka polytypic) programming in Scala
Voodoo master
'Tag' a type with additional type information
without 'boxing'
Easier if I just show you...
def template(text : String) = "<div>" + text + "</div>"
template("<script>alert('Scala is too hard')</script>")
How do we know 'string' has been escaped?
sealed trait SafeString
def escape(s : String) : String @@ SafeString = tag[SafeString](s.replace("<", "<"))
def template(text : String @@ SafeString) = "<div>" + text + "</div>"
// template("<script>alert('Scala is too hard')</script>")
template(escape("<script>alert('Scala is too hard')</script>"))
If we decompile:
public String escape(String s) {
return s.replace("<", "<");
}
def withUnboxed(i: Int @@ Foo): ...
private final void withUnboxed(int i)
{
BoxesRunTime.boxToInteger(i)
}
Scalaz use it do distinguish multiple implementations of a type class
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
}
2 |+| 3
> 5
def Multiplication[A](a: A): A @@ Multiplication = tag[Multiplication](a)
Multiplication(2) |+| Multiplication(3)
> 6
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)
"The mother of all Scala HList's"
val list = "a" :: "b" :: "c" :: Nil
> List[String]
list.map(_.length)
> List[Int]
val list = "string" :: 1 :: true :: Nil
> List[Any]
list.map {
case x : Int => x.toString
case s : String => s.length
case _ => 0
}
> List[Any]
aka HList
import shapeless._
"string" :: 1 :: true :: HNil
> String :: Int :: Boolean :: HNil
val hlist = "string" :: 1 :: true :: HNil
// def singleton[A](a: A) = Set(a)
hlist.map(singleton)
> Set[String] :: Set[Int] :: Set[Boolean] :: HNil
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]
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"))
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.
Remember
trait Function1[-T, +R] {
def apply(t : T) : R
}
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)
}
Let's use higher kinded types!
trait PolyFunctionTake1[F[_]] {
def apply[T](t : T) : F[T]
}
object singleton extends PolyFunctionTake1[Set] {
def apply[T](t : T) : Set[T] = Set(t)
}
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
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
}
Take 2
trait PolyFunction1[F[_], G[_]] {
def apply[T](f : F[T]) : G[T]
}
object headOption extends PolyFunction1[List, Option] {
def apply[T](l : List[T]) : Option[T] = l.headOption
}
What about singleton?
type Id[T] = T
object singleton extends PolyFunction1[Id, Set] {
def apply[T](t : T) : Set[T] = Set(t)
}
trait ~>[F[_], G[_]] {
def apply[T](f : F[T]) : G[T]
}
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
}
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]
Presto!
object complex extends Poly1 {
implicit def caseInt = at[Int](_.toString)
implicit def caseString = at[String](_.length)
}
val list = "a" :: 1 :: "b" :: HNil
list.map(complex)
> Int :: String :: Int :: HNil = 1 :: "1" : 1 :: HNil
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]
)))))
Cool stuff they can do
Tuples
val t1 = (23, "foo", 2.0, true)
l1 = t1.hlisted
> 23 :: "foo" :: 2.0 :: true :: HNil
> l1.tupled
> (23, "foo", 2.0, true)
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)
Free Monoids
Foo(13, "foo", 1.0) |+| Foo(23, "bar", 3.0)
> Foo(36, "foobar", 4.0)
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
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
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")
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?!?
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)
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)
}
}
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)
}
}
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)
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))