Copyright 2018 Tendril, Inc. All rights reserved....Created Date: 6/21/2018 3:08:38 PM

Preview:

Citation preview

Copyright 2018 Tendril, Inc. All rights reserved.

Copyright 2018 Tendril, Inc. All rights reserved.

Type Parameter Power-Up!

Variance, Bounds, and Inference

Copyright 2018 Tendril, Inc. All rights reserved.

Motivations

INTRO

3

Copyright 2018 Tendril, Inc. All rights reserved.

Motivations

INTRO

• Home Energy Report Generation System

• Free Monad

• Workflow stages extending common base class

• Workflow actions parameterized with a Report type

• Different Reports extend Report base class

• Single reports, Optional reports, Lists of reports

4

Copyright 2018 Tendril, Inc. All rights reserved.

Helpful Compiler is Helpful

INTRO

[info] Compiling 1 Scala source to /Users/cphelps/learning/type-parameter-power-up/target/scala-2.12/test-classes ...

[error] /Users/cphelps/learning/type-parameter-power-up/src/test/scala/example/VarianceSpec.scala:55:14: covariant type A occurs in contravariant position in type A of value newwrapped

[error] def swap(newwrapped: A): Wrapper[A] = new Wrapper(newwrapped)

[error] ^

[error] one error found

5

Copyright 2018 Tendril, Inc. All rights reserved.

Helpful Compiler is Helpful

INTRO

[info] Compiling 1 Scala source to /Users/cphelps/learning/type-parameter-power-up/target/scala-2.12/test-classes ...

[error] /Users/cphelps/learning/type-parameter-power-up/src/test/scala/example/VarianceSpec.scala:55:14: covariant type A occurs in contravariant position in type A of value newwrapped

[error] def swap(newwrapped: A): Wrapper[A] = new Wrapper(newwrapped)

[error] ^

[error] one error found

6

Copyright 2018 Tendril, Inc. All rights reserved.

Motivations

INTRO

7

Copyright 2018 Tendril, Inc. All rights reserved.

Motivations

INTRO

• Users

o Signatures of APIs– what is it looking for?

o What will the API accept?

o How do I make the API interact with my domain class hierarchy?

o How do I make sense of these weird error messages?

• API Designers

o What do I want to allow?

o What sorts of flexibility do I want to offer?

o How do I make sense of these weird error messages?

8

Copyright 2018 Tendril, Inc. All rights reserved.

“How I stopped worrying about variance: Just add +/- until it compiles” – Adriaan Moors

Copyright 2018 Tendril, Inc. All rights reserved.

Overview

INTRO

• Variance

• Constraints and Typeclasses

• Dotty / Scala 3

10

Copyright 2018 Tendril, Inc. All rights reserved.

Variance

11

Copyright 2018 Tendril, Inc. All rights reserved.

Inheritance and Substitution

VARIANCE

• All values have a type

• Types have a supertype relationship up to Any

• Types have a subtype relationship down to Nothing

• References can store subclass instances

• Functions can be passed subclass instances

• Functions can return subclass instances

12

Copyright 2018 Tendril, Inc. All rights reserved.

LiskovSubstitution Principle

VARIANCE

Subtype Requirement: Let ϕ ( x ) be a property provable about objects x of type T. Then ϕ ( y ) should be true for objects y of type S where S is a subtype of T.[Liskov and Wing, 1994]

If S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program.[Wikipedia]

13

Copyright 2018 Tendril, Inc. All rights reserved.

Higher-KindedTypes

VARIANCE

• Generic Classes

• Types with parameters

• Contain or implemented in terms of another type

o List[Int]

o List[T]

14

Copyright 2018 Tendril, Inc. All rights reserved.

Two Axes of Subclassing

VARIANCE

15

Container

Superclass

Container

Subclass

Contents

Superclass

Contents

Subclass

Copyright 2018 Tendril, Inc. All rights reserved.

Variance Defined

VARIANCE

• Variance is the correlation of subtyping relationships of complex types and the subtyping relationships of their component types. [Tour of Scala]

• A central question that comes up when mixing OO with polymorphism is: if U is a subclass of T, is Container[U] considered a subclass of Container[T]? [Twitter Scala School]

16

Copyright 2018 Tendril, Inc. All rights reserved.

Apply the LSP

VARIANCE

• Variance is the correlation of when we can substitute complex types and the when we can substitute their component types. [Tour of Scala]

• A central question that comes up when mixing OO with polymorphism is: if U is a subclass of T, can Container[U] be substituted for Container[T]? [Twitter Scala School]

17

Copyright 2018 Tendril, Inc. All rights reserved.

Substituting Compatible Machines

VARIANCE

18

Copyright 2018 Tendril, Inc. All rights reserved.

Domain Data

VARIANCE

abstract class Animal {def name: String

}

abstract class Pet extends Animal

case class Cat(name: String) extends Pet

case class Dog(name: String) extends Pet

19

Animal

Pet

DogCat

Copyright 2018 Tendril, Inc. All rights reserved.

InvarianceThe Default Case

VARIANCE

• No subclass relationship

• Cannot substitute Container[T] for Container[U] unless T is U

• Subclasses SubContainer[T] can be substituted for Container[T]

• Exposed vars must be invariant

• Influences type inference

20

Copyright 2018 Tendril, Inc. All rights reserved.

Invariance Examples

VARIANCE

class InvariantWrapper[A](wrapped: A) {def unwrapped: A = wrapped

}

val w: InvariantWrapper[Cat] = new InvariantWrapper(Cat(”Morris"))

class SubWrapper[A](wrapped: A) extends InvariantWrapper(wrapped) {}

val sw: InvariantWrapper[Cat] = new SubWrapper(Cat("Milo"))

val aw: InvariantWrapper[Animal] = new InvariantWrapper[Cat](Cat("Bill"))

21

Animal

Pet

DogCat

Copyright 2018 Tendril, Inc. All rights reserved.

Invariance Examples

VARIANCE

class InvariantWrapper[A](wrapped: A) {def unwrapped: A = wrapped

}

val w: InvariantWrapper[Cat] = new InvariantWrapper(Cat(”Morris"))

class SubWrapper[A](wrapped: A) extends InvariantWrapper(wrapped) {}

val sw: InvariantWrapper[Cat] = new SubWrapper(Cat("Milo"))

val aw: InvariantWrapper[Animal] = new InvariantWrapper[Cat](Cat("Bill"))

22

type mismatch; found: InvariantWrapper[Cat] required: InvariantWrapper[Animal]

Note: example.Cat <: example.Animal, but class InvariantWrapper is invariant in type A.

Animal

Pet

DogCat

Copyright 2018 Tendril, Inc. All rights reserved.

CovarianceSources of Values

VARIANCE

• Subclass relationship when contents have a subclass relationship

• Can substitute Container[U] for Container[T] if U is a subclass of T

• Useful for extracting the contents of the container

o Instances of U can be used where T instances are expected

o Container[U] will produce T instances

o Types are sound

• Problematic when adding or changing values

23

Copyright 2018 Tendril, Inc. All rights reserved.

Covariance Examples

VARIANCE

class CovariantWrapper[+A](wrapped: A) {def unwrapped: A = wrapped

}

def doIt(thing: CovariantWrapper[Animal]) = ???

doIt(new CovariantWrapper[Cat](Cat("Garfield")))doIt(new CovariantWrapper[Dog](Dog(“Odie”)))

doIt(new CovariantWrapper[Any](Cat("Nermal")))

24

Animal

Pet

DogCat

Copyright 2018 Tendril, Inc. All rights reserved.

Covariance Examples

VARIANCE

class CovariantWrapper[+A](wrapped: A) {def unwrapped: A = wrapped

}

def doIt(thing: CovariantWrapper[Animal]) = ???

doIt(new CovariantWrapper[Cat](Cat("Garfield")))doIt(new CovariantWrapper[Dog](Dog(“Odie”)))

doIt(new CovariantWrapper[Any](Cat("Nermal")))

25

type mismatch; found: CovariantWrapper[Any] required: CovariantWrapper[Animal]

Animal

Pet

DogCat

Copyright 2018 Tendril, Inc. All rights reserved.

ContravarianceConsumers of Values

VARIANCE

• Subclass relationship when contents have a superclass relationship

• Can substitute Container[T] for Container[U] if T is a superclass of U

• Useful for processors or consumers of values

o Instances of U can be used where T instances are expected

o Consumer[U] can consume T instances

o Types are sound

• Problematic when returning or producing values

26

Copyright 2018 Tendril, Inc. All rights reserved.

ContravarianceExamples

VARIANCE

abstract class Keeper[-A] {def tend(input: A): Unit

}class DogSitter extends Keeper[Dog] {override def tend(input: Dog): Unit = ???

}class ZooKeeper extends Keeper[Animal] { ... }class PetSitter extends Keeper[Pet] { ... }

val ds: Keeper[Dog] = new DogSitterds.tend(Dog("Tintin"))

val zoo: Keeper[Dog] = new ZooKeeperzoo.tend(Dog("Scooby"))

val petco: Keeper[Pet] = new DogSitterpetco.tend(Dog("Ceasar"))

27

Animal

Pet

DogCat

Copyright 2018 Tendril, Inc. All rights reserved.

ContravarianceExamples

VARIANCE

abstract class Keeper[-A] {def tend(input: A): Unit

}class DogSitter extends Keeper[Dog] {override def tend(input: Dog): Unit = ???

}class ZooKeeper extends Keeper[Animal] { ... }class PetSitter extends Keeper[Pet] { ... }

val ds: Keeper[Dog] = new DogSitterds.tend(Dog("Tintin"))

val zoo: Keeper[Dog] = new ZooKeeperzoo.tend(Dog("Scooby"))

val petco: Keeper[Pet] = new DogSitterpetco.tend(Dog("Ceasar"))

28

type mismatch; found : DogSitter required: Keeper[example.Pet]

Animal

Pet

DogCat

Copyright 2018 Tendril, Inc. All rights reserved.

Positions

VARIANCE

• “Covariant Position” – method returns

• “Contravariant Position” – method arguments

• “Invariant Position” - mutable vars

• Covariant parameters cannot appear in contravariant position

• Contravariant parameters cannot appear in covariant position

• Covariant or contravariant parameters cannot appear in invariant position

o Scala 2.12.6 and IntelliJ error messages misleading

29

Copyright 2018 Tendril, Inc. All rights reserved.

The Problem with Contravariant Positions

VARIANCE

class Box[+Pet](p: Pet) {// won't compile – Covariant in contravariant posdef swap(newp: Pet): Box[Pet] = new Box[Pet](newp)

}

val mypet = new Box[Cat](Cat(“Morris”))

// Seems okay so farmypet.swap(Cat("Cassie"))

// Oops, we can't store a Dog in a Box[Cat]mypet.swap(Dog("Ceasar"))

30

Copyright 2018 Tendril, Inc. All rights reserved.

The Problem with Covariant Positions

VARIANCE

class Sitter[-Pet](p: Pet) {// won't compile - contravariant in covariant posdef walk(): Pet = ???

}

val mySitter: Sitter[Cat] = new Sitter[Pet](Cat("Doraemon"))

// seems okayval walkedPet: Pet = mySitter.walk()

// if walk returns a Cat, everything is ok// if it returns a Dog, we have a problems// if it returns a Pet, we have a problem.val walked: Cat = mySitter.walk()

31

Copyright 2018 Tendril, Inc. All rights reserved.

Function Inputs

VARIANCE

val p1: Petval p2: Petf(p1, p2)

def f(a: Pet, b: Pet): Pet ✅

def f(a: Animal, b: Pet): Pet ✅

def f(a: Any, b: Pet): Pet ✅

def f(a: Any, b: Any): Pet ✅

def f(a: Cat, b: Dog): Pet ❎

Function2[-T1, -T2, R]

32

Animal

Pet

DogCat

Copyright 2018 Tendril, Inc. All rights reserved.

Function Outputs

VARIANCE

val p1: Petval p2: Pet

val r: Animal = f(p1, p2)

def f(a: Pet, b: Pet): Animal ✅

def f(a: Pet, b: Pet): Pet ✅

def f(a: Pet, b: Pet): Cat ✅

def f(a: Pet, b: Pet): Any ❎

Function2[-T1, -T2, +R]

33

Animal

Pet

DogCat

Copyright 2018 Tendril, Inc. All rights reserved.

When to Use

VARIANCE

• Covariance

o Containers

o Producers

o Representing inputs

• Contravariance

o Consumers

o Processors or Visitors

o Representing outputs

• Invariance

o Distinct types

o Markers or Labels

o Influence inference34

Copyright 2018 Tendril, Inc. All rights reserved.

Constraints and Typeclasses

35

Copyright 2018 Tendril, Inc. All rights reserved.

Upper and Lower Bounds

CONSTRAINTS AND TYPECLASSES

• Constrain the valid types

• Upper bound – parameter is same or subtype

o def pet[P <: Pet](animal: P): Unit = ???

• Lower bound – parameter is same or supertype

o def pre[B >: A](x: B, xs: List[A]): List[B] = ???

Copyright 2018 Tendril, Inc. All rights reserved.

Bounds and Variance

CONSTRAINTS AND TYPECLASSES

abstract class List[+A] { ... }

case object Nil extends List[Nothing] { ... }

case class Cons[B] (elem: B, tail: List[B])

extends List[B] { ... }

37

Copyright 2018 Tendril, Inc. All rights reserved.

Bounds and Variance

CONSTRAINTS AND TYPECLASSES

abstract class List[+A] {

def prepend(elem: A): List[A] = ???

}

case object Nil extends List[Nothing] { ... }

case class Cons[B] (elem: B, tail: List[B])

extends List[B] { ... }

38

Copyright 2018 Tendril, Inc. All rights reserved.

Bounds and Variance

CONSTRAINTS AND TYPECLASSES

abstract class List[+A] {

def prepend(elem: A): List[A] = ???

}

case object Nil extends List[Nothing] { ... }

case class Cons[B] (elem: B, tail: List[B])

extends List[B] { ... }

39

covariant type A occurs in contravariant position in type A of value elem

Copyright 2018 Tendril, Inc. All rights reserved.

Bounds and Variance

CONSTRAINTS AND TYPECLASSES

abstract class List[+A] {

def prepend[B](elem: B): List[B] =

new Cons(elem, this)

}

case object Nil extends List[Nothing] { ... }

case class Cons[B] (elem: B, tail: List[B])

extends List[B] { ... }

40

Copyright 2018 Tendril, Inc. All rights reserved.

Bounds and Variance

CONSTRAINTS AND TYPECLASSES

abstract class List[+A] {

def prepend[B](elem: B): List[B] =

new Cons(elem, this)

}

case object Nil extends List[Nothing] { ... }

case class Cons[B] (elem: B, tail: List[B])

extends List[B] { ... }

41

type mismatch; found: List[A] required: List[B]

Copyright 2018 Tendril, Inc. All rights reserved.

Bounds and Variance

CONSTRAINTS AND TYPECLASSES

abstract class List[+A] {

def prepend[B >: A] (elem: B): List[B] = ???

}

case object Nil extends List[Nothing] { ... }

case class Cons[B] (elem: B, tail: List[B])

extends List[B] { ... }

42

Copyright 2018 Tendril, Inc. All rights reserved.

Change to a Wider Type

CONSTRAINTS AND TYPECLASSES

scala> List(1, 2, 3, 4, 5)res0: List[Int] = List(1, 2, 3, 4, 5)

scala> 0 :: res0res1: List[Int] = List(0, 1, 2, 3, 4, 5)

scala> 1.5 :: res0res2: List[AnyVal] = List(1.5, 1, 2, 3, 4, 5)

scala> res2(0)res3: AnyVal = 1.5

scala> res2(1)res4: AnyVal = 1

43

Copyright 2018 Tendril, Inc. All rights reserved.

View Bounds

CONSTRAINTS AND TYPECLASSES

• Deprecated from 2.11

• [ A <% B ]

• Indicates A is convertable to B by implicit conversion

• Introduces evidence parameter

o def view[AA <% A](x: AA): A = ???

o def view$[AA](x: AA)(implicit ev1$: AA => A) = ???

44

Copyright 2018 Tendril, Inc. All rights reserved.

Context Bounds

CONSTRAINTS AND TYPECLASSES

• [ A : Ctx ]

• Indicates A has some context Ctx

o There exists a Ctx[A] in implicit scope

• Introduces an evidence parameter

o def ctxBound[X: M](x: X) = ???

o def ctxBound[X](x: X)(implicit ev1$: M[X]) = ???

• Access the context with the implicitly keyword

o val m = implicitly[M[X]]

o m.someMethod()

45

Copyright 2018 Tendril, Inc. All rights reserved.

Typeclass Pattern

CONSTRAINTS AND TYPECLASSES

• External implementation of an interface

o No need to extend directly

o Possible without access to class source

• Introduce via a context bound

• Implement in terms of of other instances

o Adder[Pair[Int]] defined in terms of Adder[Int]

46

Copyright 2018 Tendril, Inc. All rights reserved.

Typeclass Pattern

CONSTRAINTS AND TYPECLASSES

• Define an interface

• Implement for your class

• Introduce to implicit scope

• Pass to function as implicit parameter OR as type constraint

• Call methods from the interface

47

Copyright 2018 Tendril, Inc. All rights reserved.

TypeclassExample

CONSTRAINTS AND TYPECLASSES

trait Adder[A] {def add(x: A, y: A): A

}

implicit val IntAdder = new Adder[Int] {override def add(x: Int, y: Int): Int = x + y

}

def addThings[T: Adder](x: T, y: T): T = {implicitly[Adder[T]].add(x, y)

}

addThings(5, 7)

48

Copyright 2018 Tendril, Inc. All rights reserved.

Inductive TypeclassDefinition

case class Pair[T](fst: T, snd: T)

implicit def pairAdder[T: Adder] = new Adder[Pair[T]] {override def add(x: Pair[T], y: Pair[T]): Pair[T] = {val ta = implicitly[Adder[T]]Pair(ta.add(x.fst, y.fst),

ta.add(x.snd, y.snd)) }

}

49

Copyright 2018 Tendril, Inc. All rights reserved.

Dotty / Scala 3

DOTTY

Copyright 2018 Tendril, Inc. All rights reserved.

Dotty Changes

DOTTY

• “Scala 3 is fundamentally the same language as Scala 2”

• Variance and type bounds still exist – essentially the same

o Current dotc slightly different error messages

• No existential types (but other constructs instead)

• Structural types – different implementation

Copyright 2018 Tendril, Inc. All rights reserved.

Takeaways• Variance

o Substitution

o Arises naturally as a consequence of Liskov Substitution

o Deeply entwined with subclassing in higher-kinded types

• Bounds

o Interact with variance -> widening

o Interact with implicits -> typeclasses

52

Copyright 2018 Tendril, Inc. All rights reserved.

@CJPhelpsgithub.com/chrisphelps/type-parameter-power-up

Recommended