Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Be Like Water — A Shapeless Primer

Be Like Water — A Shapeless Primer

Edoardo Vacchi

February 25, 2016
Tweet

More Decks by Edoardo Vacchi

Other Decks in Programming

Transcript

  1. GENERIC PROGRAMMING example (Spire) def sum[A : Numeric](l: A, r:

    A) = l + r !""""""" "context bound" ... desugars to... def sum[A](l: A, r: A)(implicit nm: Numeric[A]) = l + r
  2. RUNNING EXAMPLE Define a node in a pipeline: case class

    Node[T, R](f: T => R) With arbitrary arity: case class Node[T1, T2, ..., TK, R](f: (T1, T2, ..., TK) => R)
  3. SHAPELESS WIKI import syntax.std.function._ import ops.function._ def applyProduct[P <: Product,

    F, L <: HList, R](p: P)(f: F) (implicit gen: Generic.Aux[P, L], fp: FnToProduct.Aux[F, L => R]) = f.toProduct(gen.to(p)) scala> applyProduct(1, 2)((_: Int)+(_: Int)) res0: Int = 3 scala> applyProduct(1, 2, 3)((_: Int)*(_: Int)*(_: Int)) res1: Int = 6
  4. HLISTS AS TUPLES val t = (1, "String", 2.0) val

    hl = 1 :: "String" :: 2.0 :: HNil val (a, b, c) = t val x :: y :: z :: HNil = hl t match { case (1, s, _) => ... } hl match { case 1 :: s :: _ :: HNil => ... }
  5. WITH ARBITRARY ARITY hl match { case 1 :: rest

    => ... // matches if first element 1, regardless the size of the list // and binds the tail to `rest` case x :: y :: HNil => ... // matches when it is a pair // etc. }
  6. FROM TUPLES TO HLISTS AND BACK AGAIN val hl =

    1 :: "String" :: 2.0 :: HNil val t : (Int, String, Double) = hl.tupled aTuple.productElements match { case a :: b :: HNil => ... } case class Person(name: String, age: Int) Person("John", 40).productElements match { case name :: age :: HNil => ... }
  7. THE Generic[T] OBJECT val gp = Generic[Person] val john =

    Person("John", 40) val hl: String :: Int :: HNil = gp.to(john) val p: Person = gp.from(hl) assert( john == p )
  8. Both case classes and tuples extend Product, so: val gp

    = Generic[Tuple2[String,Int]] val johnTuple = ("John", 40) val hl: String :: Int :: HNil = gp.to(johnTuple) val tp: Tuple2[String,Int] = gp.from(hl) assert( johnTuple == tp ) In fact, the t.productElements method is really short-hand syntax for the code snippet above.
  9. K-ARY FUNCTIONS AND HLISTS ▸ f.tupled turns a K-ary function

    into a unary function of one K-tuple argument. For instance: val f = (s: String, i: Int) => s"$s $i" val ft: Tuple2[String, Int] => String = f.tupled tuples are fixed-size! But with HLists... import syntax.std.function._ import ops.function._ val fhl: String::Int::HNil => String = f.toProduct
  10. THE FnToProduct[F] OBJECT f.toProduct is shorthand syntax to val fp

    = FnToProduct[(String, Int) => String] val fhl = fp.apply(f)
  11. BACK TO THE EXAMPLE def applyProduct[P <: Product, F, L

    <: HList, R](p: P)(f: F) (implicit gen: Generic.Aux[P, L], fp: FnToProduct.Aux[F, L => R]) = f.toProduct(gen.to(p)) // we can understand this! val gen = Generic[Person] val fhl: String :: Int :: HNil => String = f.toProduct val hl: String :: Int :: HNil = gen.to(p) fhl(hl)
  12. ▸ Implicits are a controversial feature of the Scala programming

    language. ▸ hard to grasp for beginners ▸ can be cause of headaches even to experienced users. ▸ The term implicit may be arguable
  13. IMPLICIT VALUES implicit val implicitJohn = Person("John", 40) def somePersonString(implicit

    p: Person) = p.toString somePersonString // returns "Person(John, 40)" e.g. configuration objects
  14. IMPLICIT DEFS ▸ generate implicit values as needed. implicit def

    personProvider = Person("Random Guy", (math.random * 100).toInt) def somePersonString(implicit p: Person) = p.toString > somePersonString // Person("Random Guy", and a random integer between 0 and 100)
  15. A QUICK PROLOG INTRODUCTION ▸ facts ▸ rules to derive

    new facts ▸ kept in the knowledge base (KB), ▸ Executing a Prolog program = solving the constraints in a query
  16. PROLOG V. LOGIC grandchild(A, B) :- child(A, X), child(X, B).

    ∀ A, B, X: child(A,X) ∧ child(X,B) " grandchild(A,B)
  17. QUERYING % find the pair(s) X,Y for which the grandchild

    relation holds ?- grandchild(X, Y). X = john Y = tom % find the john's grandchildren ?- grandchild(john, Y). Y = tom % find tom's grandparents ?- grandchild(X, tom). X = john
  18. QUERYING (2) % false predicates: ?- grandchild(X, X) no ?-

    grandchild(tom, X) no ?- grandchild(X, john) no
  19. DECLARING FACTS // in Logic Scala, facts are named fact

    johncarl = new Child[John,Carl]{} // an instance of type Child fact carltom = new Child[Carl,Tom ]{}
  20. DECLARING RULES trait GrandChild[A,B] rule grandChild[A,B,X]( facts xy: Child[A,X], yz:

    Child[X,B] ) = new GrandChild[A,B] {} rule[A,B,X](Child[A,X], Child[X,B]): GrandChild[A,B]
  21. QUERYING LOGIC SCALA > query[ GrandChild[John, Tom] ] // (compiles;

    returns the fact instance) > query[ GrandChild[John, Carl] ] Compilation Failed
  22. trait John trait Carl trait Tom trait Child[T,U] implicit val

    john_carl = new Child[John,Carl]{} implicit val carl_tom = new Child[Carl,Tom ]{} trait GrandChild[T,U] implicit def grandChild[X,Y,Z]( implicit xy: Child[X,Y], yz: Child[Y,Z] ) = new GrandChild[X,Z] {} > implicitly[ GrandChild[John, Tom] ] // (compiles; returns the fact instance) > implicitly[ GrandChild[John, Carl] ] Compilation Failed
  23. Scala | Prolog ---------------------------+----------------------------- implicit vals | facts implicit defs

    | rules type parameters in def | variables in a rule implicit parameter lists | bodies of a rule
  24. can_apply_product(P, F, L, R) :- generic(P,L), fn_to_product(F, fn(L, R)). def

    applyProduct[P <: Product, F, L <: HList, R](p: P)(f: F) (implicit gen: Generic.Aux[P, L], fp: FnToProduct.Aux[F, L => R] ) = f.toProduct(gen.to(p))
  25. ▸ value of an implicit parameter = evidence that a

    predicate holds ▸ when one such an object instance provides methods, then it is called a typeclass. ▸ Generic[P] provides the methods to(P) and from(HList).
  26. THE AUX ENCODING Generic.Aux and FnToProduct.Aux are actually type aliases.

    For instance, Generic.Aux is defined in the companion object of Generic object Generic { type Aux[P,L] = Generic[P] { type Out = L } ... } trait Generic[P] { type Out }
  27. Generic.Aux[P,L] is sometimes called a type extractor because it extracts

    the type member value Generic.Out by raising it into the signature of type Generic.Aux.
  28. generic(P, L) :- product(P), (X, Y) = P, hlist(L), [X,

    Y] = L. % in short: generic((X,Y), [X,Y]).
  29. % let us represent function signatures with fn fn((arg_type1, arg_type2,

    ..., arg_typeN), ret_type) fn_to_product(F, fn(L, R)) :- fn(Args,R) = F, generic(Args, L). % in short: fn_to_product(fn(Args,R), L, R) :- generic(Args, L).
  30. REFERENCES ▸ Andrea Ferretti's dependent types encoded in Scala's type

    system ▸ Mark Harrah's series on type-level programming ▸ Stefan Zeiger's slides on Type-level Computations ▸ Sam Halliday's Shapeless For Mortals ▸ Miles Sabin's introductory talks ▸ Why is the Aux technique required for type-level computations? on StackOverflow ▸ Shapeless Wiki ▸ Shapeless Examples ▸ Eugene Yokota's “Herding Cats”