scala type classes: basics and more

36

Upload: sukanthajra

Post on 16-Jul-2015

133 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Scala Type Classes:  Basics and More

Type Classes: Basics and More

Sukant Hajra

April 10, 2015

Sukant Hajra Type Classes: Basics and More April 10, 2015 1 / 36

Page 2: Scala Type Classes:  Basics and More

Outline

1 Introduction

2 Motivation by Examples

3 Encoding type classes in Scala

4 A Glance at Scalaz (Scaladoc, Demo)

5 Wrap-up

Sukant Hajra Type Classes: Basics and More April 10, 2015 2 / 36

Page 3: Scala Type Classes:  Basics and More

Section 1

Introduction

Sukant Hajra Type Classes: Basics and More April 10, 2015 3 / 36

Page 4: Scala Type Classes:  Basics and More

A show of hands

How many of you are very new to type classes?

This talk is yours, so interrupt and participate!

Sukant Hajra Type Classes: Basics and More April 10, 2015 4 / 36

Page 5: Scala Type Classes:  Basics and More

Some background

Type classes

come from Haskell

use the word �class� di�erently from OO languages

started o� a way to improve ad hoc polymorphism

evolved into a modular way to abstract invariants/contracts.

Sukant Hajra Type Classes: Basics and More April 10, 2015 5 / 36

Page 6: Scala Type Classes:  Basics and More

Types of polymorphism

parametric polymorphism (generics)

Foo[A]

subtype polymorphism

Foo extends Bar

ad hoc polymorphism (overloading)

def baz(foo: Foo): Baz

def baz(bar: Bar): Baz

Sukant Hajra Type Classes: Basics and More April 10, 2015 6 / 36

Page 7: Scala Type Classes:  Basics and More

Section 2

Motivation by Examples

Sukant Hajra Type Classes: Basics and More April 10, 2015 7 / 36

Page 8: Scala Type Classes:  Basics and More

Before we start with examples

Don't get caught up in the details

We'll cover details in a later section

Sukant Hajra Type Classes: Basics and More April 10, 2015 8 / 36

Page 9: Scala Type Classes:  Basics and More

Problem: ad hoc polymorphism

given

def size(foo: Foo): Int

def size(bar: Bar): Int

hard to generalize/extend, have to continue overloading

def size_doubled(foo: Foo) = size(foo: Foo) * 2

def size_doubled(bar: Bar) = size(bar: Bar) * 2

or maybe use a sum type like Either

def size_doubled(fooOrBar: Either[Foo, Bar]) =

fooOrBar.fold(size(_), size(_)) * 2

Sukant Hajra Type Classes: Basics and More April 10, 2015 9 / 36

Page 10: Scala Type Classes:  Basics and More

Solving with type classes: ad hoc polymorphism

we have an interface

trait HasSize[A] { def size(a: A): Int }

which we can use as a constraint

def sizeDoubled[A : HasSize](a: A) =

HasSize[A].size(a) * 2

but only if we have instances

implicit val fooHasSize: HasSize[Foo] =

new HasSize[Foo] { def size(f: Foo) = f.size }

implicit val barHasSize: HasSize[Bar] =

new HasSize[Bar] { def size(b: Bar) = b.size }

Note the nice separation of concerns.

Sukant Hajra Type Classes: Basics and More April 10, 2015 10 / 36

Page 11: Scala Type Classes:  Basics and More

type classes catch errors statically

given

case class Foo(size: Int)

case class Bar(size: Int)

Example (using HasSize)

scala> sizeDoubled(Foo(5))

res0: Int = 5

scala> sizeDoubled(Bar(3))

res0: Int = 3

scala> sizeDoubled("wat?")

<console>:25: error: could not find implicit value for

evidence parameter of type HasSize[String]

sizeDoubled("wat?")

^

Sukant Hajra Type Classes: Basics and More April 10, 2015 11 / 36

Page 12: Scala Type Classes:  Basics and More

Nicer error messages

Using @implicitNotFound on the type class interface

@annotation.implicitNotFound(

"instance of type class HasSize not found: ${A}")

trait HasSize[A] { def size(a: A): Int }

Example (we can get more human-friendly messages)

scala> sizeDoubled("wat?")

<console>:14: error: instance of type class HasSize

not found: String

sizeDoubled("wat?")

^

Sukant Hajra Type Classes: Basics and More April 10, 2015 12 / 36

Page 13: Scala Type Classes:  Basics and More

Problem: equality

A typical equals

class This {

override def equals(that: Any): Boolean =

if (! that.isInstanceOf[This])

false

else

...

}

leads to obligatory boilerplate

the compiler should sometimes prevent equality comparison

Sukant Hajra Type Classes: Basics and More April 10, 2015 13 / 36

Page 14: Scala Type Classes:  Basics and More

Solving with type classes: equality

we have an interface

trait Equal[A] { def eq(a1: A, a2: A): Boolean }

which we can use as a constraint

def member[A : Equal](as: List[A], a: A) =

as.foldLeft(false) { _ || Equal[A].eq(_, a) }

but only if we have instances

implicit val fooEqual: Equal[Foo] =

new Equal[Foo] {

def eq(f1: Foo, f2: Foo) = f1.x == f2.x

}

Sukant Hajra Type Classes: Basics and More April 10, 2015 14 / 36

Page 15: Scala Type Classes:  Basics and More

Using type classes for type enrichment

EqualOps.scala

implicit class EqualOps[A : Equal](a1: A) {

def ===(a2: A): Boolean = Equal[A].eq(a1, a2)

}

implicit vals/defs for type class instances

implicit classes for type enrichment

Sukant Hajra Type Classes: Basics and More April 10, 2015 15 / 36

Page 16: Scala Type Classes:  Basics and More

Type class equality is safer

Example (Using the equality type class)

scala> new Foo(1) == new Foo(1)

res0: Boolean = false

scala> new Foo(1) == "wat?"

res1: Boolean = false

scala> new Foo(1) === new Foo(1)

res2: Boolean = true

scala> new Foo(1) === "wat?"

<console>:24: error: type mismatch;

found : String("wat?")

required: Foo

new Foo(1) === "wat?"

Sukant Hajra Type Classes: Basics and More April 10, 2015 16 / 36

Page 17: Scala Type Classes:  Basics and More

Example: Monoid

we have an interface

trait Monoid[A] {

def mappend(a1: A, a2: A): A

def mempty: A

}

which we can use as a constraint

def empty[A : Monoid]: A = Monoid[A].mempty

implicit class MonoidOps[A : Monoid](a1: A) {

def |+|(a2: A): A = Monoid[A].mappend(a1, a2)

}

Sukant Hajra Type Classes: Basics and More April 10, 2015 17 / 36

Page 18: Scala Type Classes:  Basics and More

Monoid instances

here's some monoid instances

implicit val stringMonoid: Monoid[String] =

new Monoid[String] {

def mappend(s1: String, s2: String) = s1 + s2

def mempty = ""

}

implicit def pairMonoid[A : Monoid, B : Monoid]

: Monoid[(A, B)] =

new Monoid[(A, B)] {

def mappend(p1: (A, B), p2: (A, B)) =

(p1._1 |+| p2._1, p1._2 |+| p2._2)

def mempty = (empty[A], empty[B])

}

Sukant Hajra Type Classes: Basics and More April 10, 2015 18 / 36

Page 19: Scala Type Classes:  Basics and More

Automatic derivation

Example (Using the equality type class)

scala> "a" |+| "1"

res0: String = a1

scala> ("a", "b") |+| ("1", "2")

res1: (String, String) = (a1,b2)

scala> ("a", ("b", "c")) |+| ("1", ("2", "3"))

res2: (String, (String, String)) = (a1,(b2,c3))

scala> empty[String]

res3: String = ""

scala> empty[(String, (String, String))]

res4: (String, (String, String)) = ("", ("", ""))

Sukant Hajra Type Classes: Basics and More April 10, 2015 19 / 36

Page 20: Scala Type Classes:  Basics and More

Relation to objects

dispatch of interfaces (though static, not dynamic)

same principles, but more

single responsibilityopen/close principleinterface segregationstrong contracts

Sukant Hajra Type Classes: Basics and More April 10, 2015 20 / 36

Page 21: Scala Type Classes:  Basics and More

Lawful type classes: strong contracts

Equality Laws

For all a, b, and c of any type with an Equal constrant

Re�exivity: eq(a, a)

Symmetry: eq(a, b) ≡ eq(b, a)

Transitivity: eq(a, b) ∧ eq(b, c) ≡ eq(a, c)

Monoid Laws

For all a, b, and c of any type with a Monoid constrant

Left Identity: mappend(mempty , a) ≡ a

Right Identity: mappend(a,mempty) ≡ a

Associativity:mappend(a,mappend(b, c)) ≡ mappend(mappend(a, b), c)

Sukant Hajra Type Classes: Basics and More April 10, 2015 21 / 36

Page 22: Scala Type Classes:  Basics and More

What a type class is

De�nition (Type Class)

A type class is a constraint/interface that can be speci�ed on a typesignature for a parametric type. At the call site, the correspondinginstance/implementation is statically (at compile-time) derived, provided,and guaranteed to always be the same.

Ideally

Type classes should be lawful.

Sukant Hajra Type Classes: Basics and More April 10, 2015 22 / 36

Page 23: Scala Type Classes:  Basics and More

Example: Functor

we have an interface

trait Functor[F[_]] {

def fmap[A](fa: F[A], f: A=>B): F[B]

}

with the following laws

Identity: fmap(fa, identity) ≡ fa

Composition: fmap(fa, a andThen b) ≡ fmap(fmap(fa, a), b)

Sukant Hajra Type Classes: Basics and More April 10, 2015 23 / 36

Page 24: Scala Type Classes:  Basics and More

Is this a good type class?

type class for an isomorphism

trait Iso[A, B] {

def to(a: A): B

def from(b: B): A

}

it has laws

to(from(b)) ≡ bfrom(to(a)) ≡ a

but is it going to be unique?

Sukant Hajra Type Classes: Basics and More April 10, 2015 24 / 36

Page 25: Scala Type Classes:  Basics and More

Summary of type class bene�ts

an improved alternative to ad hoc polymorphism

nicely externally extensible (separation of concerns)

automatic derivation of type class instances

strong invariants with lawful type classes

Sukant Hajra Type Classes: Basics and More April 10, 2015 25 / 36

Page 26: Scala Type Classes:  Basics and More

Section 3

Encoding type classes in Scala

Sukant Hajra Type Classes: Basics and More April 10, 2015 26 / 36

Page 27: Scala Type Classes:  Basics and More

Uses and abuses of implicits

Good usage:

type enrichment

type class encoding

Easily abused usage:

type conversion (beyond scope, but basically an implicit function)

Terrible usage:

dependency injection framework encoding

Sukant Hajra Type Classes: Basics and More April 10, 2015 27 / 36

Page 28: Scala Type Classes:  Basics and More

Implicits, illustrated

In Scala, we encode type classes with implicits, so let's cover those �rst.

Example (implicits in Scala)

scala> case class Foo(name: String)

scala> implicit val defaultFoo = Foo("default")

scala> def fooPair(i: Int)(implicit foo: Foo) = (i, foo)

scala> fooPair(3)

res0: (Int, Foo) = (3,Foo(default))

The implicit parameter doesn't need an argument explicitly passed if a valuefor the type has been implicitly de�ned and can be found by the compiler.

Sukant Hajra Type Classes: Basics and More April 10, 2015 28 / 36

Page 29: Scala Type Classes:  Basics and More

Implicit extras

useful, and in the standard library

def implicitly[A](implicit a: A): A = a

context bound syntax sugar; these are the same

def foo[A](implicit ev: TC[A]) = ...

def foo[A : TC] = {

val ev = implicitly[TC[A]]

...

}

Sukant Hajra Type Classes: Basics and More April 10, 2015 29 / 36

Page 30: Scala Type Classes:  Basics and More

Implicit extra extras

using implicitly with context bounds is tedious

def foo[A : HasSize] = {

... implicitly[HasSize[A]].size ...

}

we can get better syntax

def foo[A : HasSize] = {

... HasSize[A].size ...

}

with an apply method on the type class's companion object

object HasSize {

def apply[A](implicit ev: HasSize[A]): HasSize[A] = ev

}

Sukant Hajra Type Classes: Basics and More April 10, 2015 30 / 36

Page 31: Scala Type Classes:  Basics and More

Implicit scope resolution for type class encoding

Implicit scope resolution is kind of complex.

For type class instances keep it simple and put them in

companion objects of the types you controlpackage objects for types you don't control

When searching for an implicit A[B], companion objects for both A

and B will be searched.

Remember to have one and only one instance to �nd (otherwise you'renot encoding a proper type class!)

Sukant Hajra Type Classes: Basics and More April 10, 2015 31 / 36

Page 32: Scala Type Classes:  Basics and More

Example encoding with companion objects

HasSize.scala

trait HasSize[A] { def size[A](a: A): Int }

object HasSize {

@inline

def apply[A](implicit ev: HasSize[A]): HasSize[A] = ev

implicit def listHasSize[A]: HasSize[List[A]] =

new HasSize[List[A]] { def size(l: List[A]) = l.size }

}

Foo.scala

case class Foo(size: Int)

object Foo {

implicit val hasSize: HasSize[Foo] =

new HasSize[Foo] { def size(f: Foo) = f.size }

}

Sukant Hajra Type Classes: Basics and More April 10, 2015 32 / 36

Page 33: Scala Type Classes:  Basics and More

Example encoding with package objects

Instances.scala

trait Instances {

implicit def listHasSize[A]: HasSize[List[A]] =

new HasSize[List[A]] { def size(l: List[A]) = l.size }

}

object Instances extends Instances

package.scala

package object myproject extends Instances

useful when you control neither the type class nor the data type

to avoid compilation complexity, some people never put instances ontype class companion objects (just the data type)

Sukant Hajra Type Classes: Basics and More April 10, 2015 33 / 36

Page 34: Scala Type Classes:  Basics and More

Section 4

A Glance at Scalaz (Scaladoc, Demo)

Sukant Hajra Type Classes: Basics and More April 10, 2015 34 / 36

Page 35: Scala Type Classes:  Basics and More

Section 5

Wrap-up

Sukant Hajra Type Classes: Basics and More April 10, 2015 35 / 36

Page 36: Scala Type Classes:  Basics and More

Thanks!

Any questions? Comments?

References

Wadler, Blott, How to make ad-hoc polymorphism less ad hoc

Hudak, Hughes, Jones, Wadler, A History of Haskell: Being Lazy With

Class

Kmett, Type Classes vs. the World,https://youtu.be/hIZxTQP1ifo

Yang, Type classes: con�uence, coherence, and global uniqueness

Jones, Jones, Meijer, Type classes: an exploration of the design space

Sukant Hajra Type Classes: Basics and More April 10, 2015 36 / 36