Top Banner
VARIANCE IN SCALA LYLE KOPNICKY JUNE 16, 2015
44

Variance in scala

Jan 22, 2018

Download

Documents

LyleK
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Variance in scala

VARIANCE IN SCALALYLE KOPNICKY

JUNE 16, 2015

Page 2: Variance in scala

OVERVIEWAlbum/Track exampleCovarianceSubtypingContravarianceInvariance

Page 3: Variance in scala

MUSIC COLLECTIONclass Album(val title: String, val tracks: Vector[Track])

class Track(val title: String, val length: Int)

Page 4: Variance in scala

DIFFERENT KINDS OF ALBUMSclass VinylAlbum(val title: String, val tracks: Vector[Track], val rpm: Int) extends Album

class MP3Album(val title: String, val tracks: Vector[Track], val bitrate: Int) extends Album

Page 5: Variance in scala

DIFFERENT KINDS OF TRACKStrait Track { val title: String val length: Int}

class VinylTrack(val title: String, val length: Int, val widthInMM: Int) extends Track

class MP3Track(val title: String, val length: Int, val path: String) extends Track

Page 6: Variance in scala

TYING TRACK SUBTYPES TO ALBUM SUBTYPEStrait Album { val title: String val tracks: Vector[Track]}

class VinylAlbum(val title: String, val tracks: Vector[VinylTrack], val rpm: Int) extends Album

class MP3Album(val title: String, val tracks: Vector[MP3Track], val bitrate: Int) extends Album

Page 7: Variance in scala

THIS IS ALLOWEDval vt1 = new VinylTrack("4′33″", 273, 5)val vt2 = new VinylTrack("0′00″", 0, 0)

val va = new VinylAlbum("Greatest Hits", Vector(vt1, vt2), 33)

val mt1 = new MP3Track("4′33″", 273, "/Music/John Cage/Greatest Hits/4′33″.mp3")val mt2 = new MP3Track("0′00″", 0, "/Music/John Cage/Greatest Hits/0′00″.mp3")

val ma = new MP3Album("Greatest Hits", Vector(mt1, mt2), 256)

Page 8: Variance in scala

THIS IS NOT ALLOWEDval vt1 = new VinylTrack("4′33″", 273, 5)val vt2 = new VinylTrack("0′00″", 0, 0)

val ma = new MP3Album("Greatest Hits", Vector(vt1, vt2), 256)

error: type mismatch; found : this.VinylTrack required: this.MP3Trackval ma = new MP3Album("Greatest Hits", Vector(vt1, vt2), 256) ̂

Page 9: Variance in scala

WHY DOES THIS WORK?1. Album is covariant with respect to each of its val fields2. Vector is covariant with respect to its type parameter

Page 10: Variance in scala

WHAT'S COVARIANCE?A relationship between compound types:

Given types ,and some compound type ,if ,

A <: BF[A]

F[A] <: F[B]then we say that is covariant in .F A

Page 11: Variance in scala
Page 12: Variance in scala

WHY IS THIS VALID?A value of a subtype can be used where a value of thesupertype is expectedThe properties of a Vector are such that the subtypingrelationship carries over from the parameterVectors are statically declared to be covariant

Page 13: Variance in scala

WHAT MAKES A SUBTYPE?Imagine a context where a value of type is expectedIf you can always use a value of type in that context,Then .

BA

A <: B

Page 14: Variance in scala

WHO DECIDES WHETHER YOU CAN?Languages may define subtyping rulesWithout such a rule, there is no subtypingIn statically-typed languages, subtype relationships mustbe declared

Page 15: Variance in scala

WIDTH SUBTYPING OF RECORDSThe subtype has additional fields:

trait Track(val: title, val length: Int)class VinylTrack(val title: String, val length: Int, val widthInMM: Int) extends Track

It makes sense to use a VinylTrack where a track is expected,because you can perform all the Track operations on it.

Page 16: Variance in scala

DEPTH SUBTYPING OF RECORDSThe subtype has fields that are subtypes of the correspondingfields in the supertype:

class TrackRating(val track: Track, val rating: Int)class VinylTrackRating(val track: VinylTrack, val rating: Int) extends TrackRating

The extends is required to statically declare therelationship.

Page 17: Variance in scala

YOU CAN'T DO THISclass TrackRating(val ratedThing: Track, val rating: Int)class AlbumRating(val ratedThing: Album, val rating: Int) extends TrackRating

Because Album is not a subtype of Track.

Page 18: Variance in scala

PROPERTIES OF A VECTORImmutableCan add or modify elements to produce a new Vector

Page 19: Variance in scala

VECTOR SUBTYPINGIf context expects a Vector[Track],it expects to be able get an element and treat it like aTrackso it's OK if that's really a VinylTrack

Page 20: Variance in scala

VECTOR SUBTYPINGIt also expects to be able to append a Vector[Track]:

v1 : Vector[VinylTrack]v2 : Vector[Track]v1 ++ v2 : Vector[Track]

Page 21: Variance in scala

VECTOR SUBTYPINGThus Vector[VinylTrack] Vector[Track]<:

It's also declared as covariant in the type: Vector[+A]

Page 22: Variance in scala

ALBUM SUBTYPINGCombined width & depth subtyping:

trait Album { val title: String val tracks: Vector[Track]}

class VinylAlbum(val title: String, val tracks: Vector[VinylTrack], val rpm: Int) extends Album

Page 23: Variance in scala
Page 24: Variance in scala

ALBUM WITH A TYPE PARAMETERYou could instead turn the Track type into a constrainedparameter:

trait Album[+T <: Track] { val title: String val tracks: Vector[T]}

class VinylAlbum(val title: String, val tracks: Vector[VinylTrack], val rpm: Int) extends Album[VinylTrack]

This works essentially the same, but creates additionalintermediate types, such as Album[VinylTrack].

Page 25: Variance in scala

CONTRAVARIANCEThe reverse of covarianceIf , then Commonly occurs in function parameters

A <: B F[B] <: F[A]

Page 26: Variance in scala

FUNCTION SUBTYPINGtype Predicate[A] = A -> Boolean

def spinsFast(album: VinylAlbum): Boolean = { rpm > 33 }

def isBigAlbum(album: Album): Boolean = { tracks.length > 10 }

Given , Is Predicate[A] Predicate[B]?A <: B <:

Page 27: Variance in scala

FUNCTION SUBTYPINGContext expects a Predicate[Album]. Can we provide aPredicate[VinylAlbum], such as spinsFast?

No, because the context might apply it to an MP3Album,which has no rpm field.

Page 28: Variance in scala

FUNCTION SUBTYPINGContext expects a Predicate[VinylAlbum]. Can weprovide a Predicate[Album], such as isBigAlbum?

Yes, because even an MP3Album has a number of tracks, asdo all Albums.

Page 29: Variance in scala

FUNCTION SUBTYPINGSo, when , Predicate[B] Predicate[A].A <: B <:

We say that Predicate is contravariant in its typeparameter.

We should declare it as

type Predicate[-A] = A -> Boolean

Page 30: Variance in scala
Page 31: Variance in scala

VARIANCE IN FUNCTIONSIn general, the function type is contravariant in andcovariant in .

A → B AB

A type that is contravariant in one paramter and covariant inthe other is called provariant. Map[-A, +B] is anotherexample.

For multiple input parameters, function types arecontravariant in all parameter types.

Page 32: Variance in scala

VARIANCE IN METHODSMethods are like functions, but also have a self type.You might think of self as a hidden parameter, andtherefore contravariant.But methods are covariant in the self type because it isused to dispatch to the appropriate subclass.In languages with multiple dispatch, all parameters used fordispatch are covariant.

Page 33: Variance in scala

COVARIANT AND CONTRAVARIANT POSITIONSEvery class/trait declaration has covariant and contravariantpositions for types:

trait Album { val title: String val tracks: Vector[Track] def playOn(player: Player): Unit def isBig: Boolean}

vals (title, tracks) are in covariant positionmethod arguments (player) are in contravariant positionmethod return types are in covariant position

Page 34: Variance in scala

COVARIANT AND CONTRAVARIANT POSITIONSSo, we couldn't use subtypes of Player in a subtype, right?

class VinylPlayer(...) extends Player

class VinylAlbum(val title: String, val tracks: Vector[VinylTrack]) { def playOn(player: VinylPlayer): Unit { ... }}

Turns out you can, but it's a separate method overloading

Page 35: Variance in scala

COVARIANT AND CONTRAVARIANT POSITIONSWhen creating a subtype, the types in each position arechecked against the supertype, to satisfy the variance rules.

Type parameters must simultaneously meet the criteria forall positions where they are used.

Page 36: Variance in scala

COVARIANT AND CONTRAVARIANT POSITIONSTry the track type as a parameter:

trait Album[+T <: Track] { val title: String val tracks: Vector[T] def isLongerThan(otherAlbum: Album[T]): Boolean}

Illegal: Parameter is declared covariant but used in acontravariant position

Page 37: Variance in scala

INVARIANCEWe can use the type parameter in both positions if we give upcovariance:

trait Album[T <: Track] { val title: String val tracks: Vector[T] def isLongerThan(otherAlbum: Album[T]): Boolean}

Then Album[VinylTrack] is neither a subtype nor asupertype of Album[Track].

Page 38: Variance in scala

INVARIANCEBut this is OK:

trait Album { val title: String val tracks: Vector[Track] def isLongerThan(otherAlbum: Album): Boolean}

VinylAlbum.isLongerThan must also accept all Albums

Page 39: Variance in scala

INVARIANCEAll var types are invariant positions, since they serve as boththe return type of a getter and the parameter type of a setter.

trait Album { val title: String var tracks: Vector[Track]}

This would make Album invariant in Vector[Track], sosubtypes couldn't specialize.

Page 40: Variance in scala

INVARIANCEThinking of fields as getters/setters:

trait Album { def title: String def tracks: Vector[Track] def tracks=(Vector[Track]): Unit}

Since Vector[Track] appears in both covariant andcontravariant positions, subtypes can't specialize.

Page 41: Variance in scala

INVARIANCE OF MUTABLE TYPESLike a var, Array[T] must be invariant in T. Think of how itwould go wrong if Array were covariant:

val vt1: VinylTrack = ...val mta = Array.empty: Array[MP3Track]mta(0) = vt1 # BAD

You can't use an Array[MP3Track] in place of anArray[Track].

Page 42: Variance in scala

INVARIANCE OF MUTABLE TYPESNow think of how it would go wrong if Array werecontravariant:

val vt1: VinylTrack = ...val ta = Array(vt1): Array[Track]val mt = ta(0): MP3Track # BADval p = mt.path

You can't use an Array[Track] in place of anArray[MP3Track].

Page 43: Variance in scala

KEY POINTSWhen using extends, component types are checkedThose in covariant position must be subtypesThose in contravariant position must be supertypesType parameters can be declared as covariant orcontravariantThese declarations will be checked against positionsParameters in both positions must be invariant

Page 44: Variance in scala

OTHER TOPICSNested levels of contravariance flipping each other(function of function)Binary methodsUseful overloading