Slide 1

Slide 1 text

BE LIKE WATER A Shapeless Primer

Slide 2

Slide 2 text

@evacchi — twitter, github read more on rnduja.github.io

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

INTRODUCTION

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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)

Slide 7

Slide 7 text

without special-casing Function1[T,R] Function2[T1,T2,R] Tuple2[T1,T2] ... ... Function22[T1,T2,...,T22,R] Tuple22[T1,T2,...,T22]

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

WTF?

Slide 10

Slide 10 text

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 => ... }

Slide 11

Slide 11 text

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. }

Slide 12

Slide 12 text

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 => ... }

Slide 13

Slide 13 text

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 )

Slide 14

Slide 14 text

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.

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

THE FnToProduct[F] OBJECT f.toProduct is shorthand syntax to val fp = FnToProduct[(String, Int) => String] val fhl = fp.apply(f)

Slide 17

Slide 17 text

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)

Slide 18

Slide 18 text

IMPLICITS

Slide 19

Slide 19 text

▸ 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

Slide 20

Slide 20 text

IMPLICIT VALUES implicit val implicitJohn = Person("John", 40) def somePersonString(implicit p: Person) = p.toString somePersonString // returns "Person(John, 40)" e.g. configuration objects

Slide 21

Slide 21 text

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)

Slide 22

Slide 22 text

IMPLICIT PARAMETERS AS PREDICATES Enforce extra compile-time constraints ▸ Generic.Aux[P,L] encodes a predicate on P,L

Slide 23

Slide 23 text

PROLOG

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

EXAMPLE person(john). person(carl). person(tom).

Slide 26

Slide 26 text

?- person(john). true ?- person(X). X = john ; X = carl ; X = tom

Slide 27

Slide 27 text

child, grandchild child(john, carl). child(carl, tom). grandchild(A, B) :- child(A, X), child(X, B).

Slide 28

Slide 28 text

PROLOG V. LOGIC grandchild(A, B) :- child(A, X), child(X, B). ∀ A, B, X: child(A,X) ∧ child(X,B) " grandchild(A,B)

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

QUERYING (2) % false predicates: ?- grandchild(X, X) no ?- grandchild(tom, X) no ?- grandchild(X, john) no

Slide 31

Slide 31 text

mortal(X) :- person(X). person(sokrates). ?- mortal(sokrates). true

Slide 32

Slide 32 text

INTRODUCING LOGIC SCALA ▸ A superset of Scala with 2 new keywords ▸ fact ▸ rule

Slide 33

Slide 33 text

DECLARING RELATIONS AND ATOMS trait Child[A,B] trait John trait Carl trait Tom

Slide 34

Slide 34 text

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 ]{}

Slide 35

Slide 35 text

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]

Slide 36

Slide 36 text

QUERYING LOGIC SCALA > query[ GrandChild[John, Tom] ] // (compiles; returns the fact instance) > query[ GrandChild[John, Carl] ] Compilation Failed

Slide 37

Slide 37 text

PLOT TWIST: LOGIC SCALA is ACTUALLY SCALA

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Scala | Prolog ---------------------------+----------------------------- implicit vals | facts implicit defs | rules type parameters in def | variables in a rule implicit parameter lists | bodies of a rule

Slide 41

Slide 41 text

applyProduct

Slide 42

Slide 42 text

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))

Slide 43

Slide 43 text

SECOND PLOT TWIST: Generic[P] AND FnToProduct[F] ARE TYPECLASSES

Slide 44

Slide 44 text

▸ 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).

Slide 45

Slide 45 text

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 }

Slide 46

Slide 46 text

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.

Slide 47

Slide 47 text

BONUS TRACK

Slide 48

Slide 48 text

generic(P, L) :- product(P), (X, Y) = P, hlist(L), [X, Y] = L. % in short: generic((X,Y), [X,Y]).

Slide 49

Slide 49 text

% 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).

Slide 50

Slide 50 text

generic((X,Y), [X,Y]). fn_to_product(fn(Args,R), L, R) :- generic(Args, L). can_apply_product(P, F, L, R) :- generic(P,L), fn_to_product(F, fn(L, R)).

Slide 51

Slide 51 text

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”