Share
Embed
Introduction to Shapeless
An introduction to using the Shapeless Library for the Scala programming language
Nigel Warren
March 17, 2015
Transcript
20 slides
-
Shapeless “Since [2011] Shapeless has evolved from being a
resolutely experimental project into library which, while s+ll tes+ng the limits of what's possible in Scala, is being used widely in produc+on systems wherever there are ari+es to be abstracted over and boilerplate to be scrapped.” hIps:/ /github.com/milessabin/shapeless -
Example: Solvers In our case : AIempt to find
the result (root), as the nearest value to 0, of a func+on by repeatedly supplying varying parameter values to the func+on. Parameters values range 0.0 -‐> 1.0 Parameter arity 1 ~> 10 hIp:/ /en.wikipedia.org/wiki/Solver -
Target func+on def func(a : Double, b :
Double, c : Double) : Double = (a - 0.2) + (b - 0.5) + (c - 0.7) The underlying Java port of solver uses Arrays to express the root hints, the func+on parameters and the parameters that get nearest to the func+ons root. def solveInternal( hint : Array[Double], func : (Array[Double]) => Double) : Array[Double] -
Bug ! The ‘hint’ array was arity 3 and
Func+on was arity 4. Need to ‘lock together’ the arity of the hint, func+on parameters, and resul+ng array. Catch all of these errors at compile +me. Use the compiler to eliminate all of the ‘arity mismatch’ test cases. -
First ‘Solu+on’ def solve( hint : Double, func
: (Double) => Double) : Double def solve( hint : (Double, Double), func : (Double, Double) => Double) : (Double, Double) def solve( hint : (Double, Double, Double), func : (Double, Double, Double) => Double ) : (Double, Double, Double) def solve( hint : (Double, Double, Double, Double), func : (Double, Double, Double, Double) => Double) : (Double, Double, Double, Double) -
But … What if we could encode Natural numbers
into the type system and use these to constrain the size of the Arrays. Shapeless Has … • ‘Nat’ -‐ natural number type encodings • ‘Sized’ -‐ compile +me sized collec+ons -
import shapeless._ val hints = Sized(0.5, 0.5, 0.5) def toBeSolved(params
: Sized[IndexedSeq[Double], nat._3]) : Double = { val a = params at Nat(0) import nat._ val b = params at _1 val c = params(_2) (a - 0.2) + (b - 0.5) + (c - 0.7) } val s = solve( hints, toBeSolved ) -
import shapeless._ val hints = Sized(0.5, 0.5, 0.5, 0.5) def
toBeSolved(params : Sized[IndexedSeq[Double], nat._3]) : Double = { ??? } val s = solve( hints, toBeSolved ) [error] Solver.scala:75: type mismatch; [error] found:shapeless.Sized[IndexedSeq[Double],shapeless.nat._4] [error] required: shapeless.Sized[IndexedSeq[Double], … ] -
import shapeless._ import nat._ val hints = Sized(0.5, 0.5, 0.5)
def toBeSolved(params : Sized[IndexedSeq[Double], _3]) : Double = { val (a,b,c) = params.toHList.tupled (a - 0.2) + (b - 0.5) + (c - 0.7) } val s = solve( hints, toBeSolved ) -
HLists Tuples • Fixed length sequence of dis+nct
types Collec+ons • Varying length sequence of elements of the same types HLists • Varying length sequence of elements of dis+nct types -
Polymorphic Func+on Values How do we map, fold etc over
an HList using, and transforming / preserving element types ? -
object size extends Poly1 { implicit def caseInt = at[Int](
x => 4 ) implicit def caseString = at[String]( 2 * _.length + 16) implicit def caseBoolean = at[Boolean] ( x => 4 ) } val hl = 23 :: "Ola" :: true :: HNil val sum = hl.map(size).toList.sum object rotator extends Poly1 { implicit def caseInt = at[Int]( x => true ) implicit def caseString = at[String]( x => 1) implicit def caseBoolean = at[Boolean] ( x => "True" ) } -
HLists in Use Mathias Doenitz has used HLists in Spray
Rou+ng and Parboiled 2. In parsing the DSLs parboiled2 pushes values onto an HList stack and pops them as required, as parameter to func+ons for example. hIps:/ /github.com/sirthias/parboiled2 -
HLists in Use Joni Freeman's “sqltyped” library
makes extensive use of shapeless extensible records. hIps:/ /github.com/jonifreeman/sqltyped also see ‘doobie’ project. -
HLists in Use Scodec is a a combinator library for
purely func+on encoding/decoding of binary data. Uses HLists to do automa+c case class binding. case class Point(x: Int, y: Int, z: Int) val pointCodec = (int8 :: int8 :: int8).as[Point]) hIps:/ /github.com/scodec/scodec