scala type classes: basics and more

Post on 16-Jul-2015

138 Views

Category:

Software

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Type Classes: Basics and More

Sukant Hajra

April 10, 2015

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

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

Section 1

Introduction

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

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

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

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

Section 2

Motivation by Examples

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Section 3

Encoding type classes in Scala

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

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

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

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

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

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

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

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

Section 4

A Glance at Scalaz (Scaladoc, Demo)

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

Section 5

Wrap-up

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

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

top related