variance in scala
TRANSCRIPT
![Page 1: Variance in scala](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/1.jpg)
VARIANCE IN SCALALYLE KOPNICKY
JUNE 16, 2015
![Page 2: Variance in scala](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/2.jpg)
OVERVIEWAlbum/Track exampleCovarianceSubtypingContravarianceInvariance
![Page 3: Variance in scala](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/3.jpg)
MUSIC COLLECTIONclass Album(val title: String, val tracks: Vector[Track])
class Track(val title: String, val length: Int)
![Page 4: Variance in scala](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/4.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/5.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/6.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/7.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/8.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/9.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/10.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/11.jpg)
![Page 12: Variance in scala](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/12.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/13.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/14.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/15.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/16.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/17.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/18.jpg)
PROPERTIES OF A VECTORImmutableCan add or modify elements to produce a new Vector
![Page 19: Variance in scala](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/19.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/20.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/21.jpg)
VECTOR SUBTYPINGThus Vector[VinylTrack] Vector[Track]<:
It's also declared as covariant in the type: Vector[+A]
![Page 22: Variance in scala](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/22.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/23.jpg)
![Page 24: Variance in scala](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/24.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/25.jpg)
CONTRAVARIANCEThe reverse of covarianceIf , then Commonly occurs in function parameters
A <: B F[B] <: F[A]
![Page 26: Variance in scala](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/26.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/27.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/28.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/29.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/30.jpg)
![Page 31: Variance in scala](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/31.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/32.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/33.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/34.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/35.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/36.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/37.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/38.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/39.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/40.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/41.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/42.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/43.jpg)
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](https://reader036.vdocuments.us/reader036/viewer/2022062504/5a6579787f8b9a931a8b5df1/html5/thumbnails/44.jpg)
OTHER TOPICSNested levels of contravariance flipping each other(function of function)Binary methodsUseful overloading