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 3
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 4
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
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? 7
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 11
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] 12
• 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] 15
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] 16
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 18 Animal Pet Dog Cat
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 19
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")) 20 Animal Pet Dog Cat
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 type mismatch; found: InvariantWrapper[Cat] required: InvariantWrapper[Animal] Note: example.Cat <: example.Animal, but class InvariantWrapper is invariant in type A. Animal Pet Dog Cat
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 22
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 25
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 DogSitter ds.tend(Dog("Tintin")) val zoo: Keeper[Dog] = new ZooKeeper zoo.tend(Dog("Scooby")) val petco: Keeper[Pet] = new DogSitter petco.tend(Dog("Ceasar")) 26 Animal Pet Dog Cat
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 DogSitter ds.tend(Dog("Tintin")) val zoo: Keeper[Dog] = new ZooKeeper zoo.tend(Dog("Scooby")) val petco: Keeper[Pet] = new DogSitter petco.tend(Dog("Ceasar")) 27 type mismatch; found : DogSitter required: Keeper[example.Pet] Animal Pet Dog Cat
Contravariant Positions VARIANCE class Box[+Pet](p: Pet) { // won't compile – Covariant in contravariant pos def swap(newp: Pet): Box[Pet] = new Box[Pet](newp) } val mypet = new Box[Cat](Cat(“Morris”)) // Seems okay so far mypet.swap(Cat("Cassie")) // Oops, we can't store a Dog in a Box[Cat] mypet.swap(Dog("Ceasar")) 29
Covariant Positions VARIANCE class Sitter[-Pet](p: Pet) { // won't compile - contravariant in covariant pos def walk(): Pet = ??? } val mySitter: Sitter[Cat] = new Sitter[Pet](Cat("Doraemon")) // seems okay val 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() 30
val p1: Pet val p2: Pet f(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] 31 Animal Pet Dog Cat
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 inference 33
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] = ???
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 covariant type A occurs in contravariant position in type A of value elem
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) = ??? 43
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() 44
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] 45
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 46
• “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
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 51