@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
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
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
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 ]{}
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
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).
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”