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

Be LIke Water (Scala Italy 2016)

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Be LIke Water (Scala Italy 2016)

Updated (and bugfixed) version of the slides for the Shapeless Talk @ Scala Italy 2016

Avatar for Edoardo Vacchi

Edoardo Vacchi

May 14, 2016
Tweet

More Decks by Edoardo Vacchi

Other Decks in Programming

Transcript

  1. A PIPELINE $ find ~ | xargs grep hello |

    wc -l case class Node[T, R](f: T => R) // then, for each input t: T val output = f(t)
  2. WE KNOW HOW TO DO THIS WITH fixed ARITY /*

    * ### E.g: ARITY 2 * * f is a **binary** function */ case class Node[T1,T2,R](f:(T1,T2) => R) val in1 = readInput1() val in2 = readInput2() // f.tupled is the **unary** function that takes the **pair** (T1,T2) val ft: Tuple2[T1, T2] => R = f.tupled ft( (in1, in2) ) // apply f.tupled to the pair
  3. ARBITRARY ARITY case class Node[T1, T2, ..., TK, R](f: (T1,

    T2, ..., TK) => R) // 1) how do you construct a K-tuple, for any K ? // 2) what would the type signature for Node be ? Function1[T,R] Function2[T1,T2,R] Tuple2[T1,T2] ... ... Function22[T1,T2,...,T22,R] Tuple22[T1,T2,...,T22]
  4. - Code Generation: (macros ?) - Error prone. How to

    test ? - Reflection - Runtime errors Maybe Shapeless could help
  5. SHAPELESS WIKI «FACILITIES FOR ABSTRACTING OVER ARITY» 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
  6. «FACILITIES FOR ABSTRACTING OVER ARITY» «Conversions between tuples and HList's,

    and between ordinary Scala functions of arbitrary arity and functions which take a single corresponding HList argument allow higher order functions to abstract over the arity of the functions and values they are passed»
  7. LISTS AND HETEROGENEOUS LISTS (HLISTS) val l = 1 ::

    "String" :: 2.0 :: Nil scala> val listFirst = l(0) listFirst: Any = 1 val hl = 1 :: "String" :: 2.0 :: HNil hl match { case x :: xs => ... // matches head and tail } scala> val first = hl(0) first: Int = 1 scala> val second = hl(1) second: String = "String" HLists also support map, folds, etc.
  8. 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 => ... }
  9. WITH arbitrary arity ! hl match { case 1 ::

    rest => ... // matches when head == 1 (any size) case x :: y :: rest => ... // matches size >= 2 case x :: y :: HNil => ... // matches when it is exactly a pair }
  10. CONVERSIONS BETWEEN TUPLES, CASE CLASSES AND HLISTS val hl: String

    :: Int :: HNil = 1 :: "String" :: 2.0 :: HNil val t: (String, Int) = hl.tupled // to tuple val hl2: String :: Int :: HNil = t.productElements // to HList val p: Person = Person("John", 30) val hl3: String :: Int :: HNil = p.productElements // to HList
  11. THE Generic[T] OBJECT // hl.tupled and t.productElements methods // are

    short-hands for: 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 ) // works for tuples as well val gp = Generic[(String,Int)] val johnTuple = ("John", 40) val hl: String :: Int :: HNil = gp.to(johnTuple) val tp: (String,Int) = gp.from(hl) assert( johnTuple == tp )
  12. ▸ Tuples and case classes implement the Product trait ▸

    You can convert between HLists, Tuples and case classes This is no surprise: they are all product types Similar to Cartesian product: A x B = { (a,b) | a ∊ A, b ∊ B }
  13. /* * `Generic[P]` (here `Generic.Aux[P,L]`) * - converts between product

    types * - enables the `t.toProduct` syntax * What about `FnToProduct` ? */ 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) )((x: Int, y: Int) => x + y) res0: Int = 3
  14. K-ARY FUNCTIONS AND HLISTS f.tupled for a K-ary function =

    a unary function of one K-tuple tuples are fixed-size! we cannot partially match; but with HLists... import syntax.std.function._ import ops.function._ val fhl: String::Int::HNil => String = f.toProduct f.toProduct is shorthand syntax for val fp = FnToProduct[(String, Int) => String] val fhl = fp.apply(f)
  15. 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)
  16. AN IMPLEMENTATION DETAIL: THE AUX ENCODING /* * When you

    create a Generic[P] instance * you want to let Scala infer the result HList type `L`. */ val gen = Generic[Person] // otherwise you would have to spell it out val gen = Generic[Person, String :: Int :: HNil] /* * when `Generic` is used to **enforce a constraint**, * then you might want to use `Generic.Aux[P,L]`. * * `Generic.Aux[P,L]` reads as "that `Generic[P]` instance that converts P into L" * FnToProduct.Aux[F, L => R] reads as "that FnToProduct[F] that converts F into L => R" */
  17. wait, what ? are you saying that implicits can be

    used to enforce compile-time constraints ? how ?
  18. IMPLICITS ▸ Controversial feature ▸ Hard to grasp for beginners

    ▸ Can cause headaches even to experienced users. ▸ The term implicit may be arguable
  19. IMPLICIT VALUES & IMPLICIT DEFS // e.g. config objects implicit

    val implicitJohn = Person("John", 40) def somePersonString(implicit p: Person) = p.toString scala> somePersonString res0: String = "Person(John,40)" // *generate* values as they are needed implicit def personProvider = Person("Random Guy", (math.random * 100).toInt) def somePersonString(implicit p: Person) = p.toString scala> somePersonString res1: String = "Person(Random Guy,26)" scala> somePersonString res2: String = "Person(Random Guy,31)"
  20. A QUICK PROLOG INTRODUCTION ▸ facts ▸ rules to derive

    new facts ▸ contained in the knowledge base (KB) ▸ Executing a Prolog program = solving the constraints in a query
  21. Every man is mortal Sokrates is a man => Sokrates

    is mortal ∀ x: person(x) ! mortal(x) => person(sokrates) ! mortal(sokrates)
  22. child, grandchild child(john, carl). child(carl, tom). grandchild(A, B) :- child(A,

    X), child(X, B). ∀ A, B, X: child(A,X) ∧ child(X,B) " grandchild(A,B)
  23. % find the pair(s) X,Y % for which the 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
  24. 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 ]{} you are introducing into the knowledge base concrete evidences, witnesses (obj instances) that these facts hold true
  25. DECLARING RULES trait GrandChild[A,B] // in Logic Scala, even rules

    are named 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]
  26. QUERYING LOGIC SCALA > query[ GrandChild[John, Tom] ] // (compiles;

    returns the fact instance) > query[ GrandChild[John, Carl] ] Compilation Failed
  27. 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
  28. Scala | Prolog ---------------------------+----------------------------- implicit vals | facts implicit defs

    | rules type parameters in def | variables in a rule implicit parameter lists | bodies of a rule
  29. 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)) scala> applyProduct(1, 2)((x: Int, y: Int) => x + y) res0: Int = 3
  30. TYPECLASSES AS PREDICATES ▸ 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).
  31. 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”
  32. AN IMPLEMENTATION DETAIL: THE AUX ENCODING /* * When you

    create a Generic[P] instance, * you generally **do not** want to specify * the result HList type `L`. */ val gen = Generic[Person] // otherwise you would have to write val gen = Generic[Person, String :: Int :: HNil] /* * when `Generic` is used as a **predicate** (as an **implicit**), * then you can use `Generic.Aux[P,L]`. * You can think of it as if it were defined as follows (pseudo-code) */ trait Generic[P] { type Out <: HList } type Generic.Aux[P,L] = Generic[P] { type Out = L }
  33. 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.
  34. PROLOG ENCODING generic(P, L) :- product(P), (X, Y) = P,

    hlist(L), [X, Y] = L. % in short: generic((X,Y), [X,Y]).
  35. % 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).