SlideShare a Scribd company logo
VARIANCE IN SCALA
LYLE KOPNICKY
JUNE 16, 2015
OVERVIEW
Album/Track example
Covariance
Subtyping
Contravariance
Invariance
MUSIC COLLECTION
class Album(val title: String, val tracks: Vector[Track])
class Track(val title: String, val length: Int)
DIFFERENT KINDS OF ALBUMS
class 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
DIFFERENT KINDS OF TRACKS
trait 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
TYING TRACK SUBTYPES TO ALBUM SUBTYPES
trait 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
THIS IS ALLOWED
val 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)
THIS IS NOT ALLOWED
val 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.MP3Track
val ma = new MP3Album("Greatest Hits", Vector(vt1, vt2), 256)
^
WHY DOES THIS WORK?
1. Albumis covariant with respect to each of its valfields
2. Vectoris covariant with respect to its type parameter
WHAT'S COVARIANCE?
A relationship between compound types:
Given types ,
and some compound type ,
if ,
A <: B
F[A]
F[A] <: F[B]
then we say that is covariant in .F A
Variance in scala
WHY IS THIS VALID?
A value of a subtype can be used where a value of the
supertype is expected
The properties of a Vector are such that the subtyping
relationship carries over from the parameter
Vectors are statically declared to be covariant
WHAT MAKES A SUBTYPE?
Imagine a context where a value of type is expected
If you can always use a value of type in that context,
Then .
B
A
A <: B
WHO DECIDES WHETHER YOU CAN?
Languages may define subtyping rules
Without such a rule, there is no subtyping
In statically-typed languages, subtype relationships must
be declared
WIDTH SUBTYPING OF RECORDS
The 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.
DEPTH SUBTYPING OF RECORDS
The subtype has fields that are subtypes of the corresponding
fields in the supertype:
class TrackRating(val track: Track, val rating: Int)
class VinylTrackRating(val track: VinylTrack, val rating: Int)
extends TrackRating
The extendsis required to statically declare the
relationship.
YOU CAN'T DO THIS
class TrackRating(val ratedThing: Track, val rating: Int)
class AlbumRating(val ratedThing: Album, val rating: Int)
extends TrackRating
Because Albumis not a subtype of Track.
PROPERTIES OF A VECTOR
Immutable
Can add or modify elements to produce a new Vector
VECTOR SUBTYPING
If context expects a Vector[Track],
it expects to be able get an element and treat it like a
Track
so it's OK if that's really a VinylTrack
VECTOR SUBTYPING
It also expects to be able to append a Vector[Track]:
v1 : Vector[VinylTrack]
v2 : Vector[Track]
v1 ++ v2 : Vector[Track]
VECTOR SUBTYPING
Thus Vector[VinylTrack] Vector[Track]<:
It's also declared as covariant in the type: Vector[+A]
ALBUM SUBTYPING
Combined 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
Variance in scala
ALBUM WITH A TYPE PARAMETER
You could instead turn the Track type into a constrained
parameter:
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 additional
intermediate types, such as Album[VinylTrack].
CONTRAVARIANCE
The reverse of covariance
If , then
Commonly occurs in function parameters
A <: B F[B] <: F[A]
FUNCTION SUBTYPING
type 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 <:
FUNCTION SUBTYPING
Context expects a Predicate[Album]. Can we provide a
Predicate[VinylAlbum], such as spinsFast?
No, because the context might apply it to an MP3Album,
which has no rpmfield.
FUNCTION SUBTYPING
Context expects a Predicate[VinylAlbum]. Can we
provide a Predicate[Album], such as isBigAlbum?
Yes, because even an MP3Albumhas a number of tracks, as
do all Albums.
FUNCTION SUBTYPING
So, when , Predicate[B] Predicate[A].A <: B <:
We say that Predicateis contravariant in its type
parameter.
We should declare it as
type Predicate[-A] = A -> Boolean
Variance in scala
VARIANCE IN FUNCTIONS
In general, the function type is contravariant in and
covariant in .
A → B A
B
A type that is contravariant in one paramter and covariant in
the other is called provariant. Map[-A, +B]is another
example.
For multiple input parameters, function types are
contravariant in all parameter types.
VARIANCE IN METHODS
Methods are like functions, but also have a selftype.
You might think of selfas a hidden parameter, and
therefore contravariant.
But methods are covariant in the selftype because it is
used to dispatch to the appropriate subclass.
In languages with multiple dispatch, all parameters used for
dispatch are covariant.
COVARIANT AND CONTRAVARIANT POSITIONS
Every class/trait declaration has covariant and contravariant
positions 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 position
method arguments (player) are in contravariant position
method return types are in covariant position
COVARIANT AND CONTRAVARIANT POSITIONS
So, 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
COVARIANT AND CONTRAVARIANT POSITIONS
When creating a subtype, the types in each position are
checked against the supertype, to satisfy the variance rules.
Type parameters must simultaneously meet the criteria for
all positions where they are used.
COVARIANT AND CONTRAVARIANT POSITIONS
Try 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 a
contravariant position
INVARIANCE
We can use the type parameter in both positions if we give up
covariance:
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 a
supertype of Album[Track].
INVARIANCE
But this is OK:
trait Album {
val title: String
val tracks: Vector[Track]
def isLongerThan(otherAlbum: Album): Boolean
}
VinylAlbum.isLongerThanmust also accept all Albums
INVARIANCE
All vartypes are invariant positions, since they serve as both
the return type of a getter and the parameter type of a setter.
trait Album {
val title: String
var tracks: Vector[Track]
}
This would make Albuminvariant in Vector[Track], so
subtypes couldn't specialize.
INVARIANCE
Thinking 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 and
contravariant positions, subtypes can't specialize.
INVARIANCE OF MUTABLE TYPES
Like a var, Array[T]must be invariant in T. Think of how it
would go wrong if Arraywere covariant:
val vt1: VinylTrack = ...
val mta = Array.empty: Array[MP3Track]
mta(0) = vt1 # BAD
You can't use an Array[MP3Track]in place of an
Array[Track].
INVARIANCE OF MUTABLE TYPES
Now think of how it would go wrong if Arraywere
contravariant:
val vt1: VinylTrack = ...
val ta = Array(vt1): Array[Track]
val mt = ta(0): MP3Track # BAD
val p = mt.path
You can't use an Array[Track]in place of an
Array[MP3Track].
KEY POINTS
When using extends, component types are checked
Those in covariant position must be subtypes
Those in contravariant position must be supertypes
Type parameters can be declared as covariant or
contravariant
These declarations will be checked against positions
Parameters in both positions must be invariant
OTHER TOPICS
Nested levels of contravariance flipping each other
(function of function)
Binary methods
Useful overloading
Ad

Recommended

Types by Adform Research, Saulius Valatka
Types by Adform Research, Saulius Valatka
Vasil Remeniuk
 
Big Data Day LA 2016/ Hadoop/ Spark/ Kafka track - Data Provenance Support in...
Big Data Day LA 2016/ Hadoop/ Spark/ Kafka track - Data Provenance Support in...
Data Con LA
 
Effective Programming In Scala
Effective Programming In Scala
Harsh Sharma
 
Type Parameterization
Type Parameterization
Knoldus Inc.
 
Scala collections
Scala collections
Inphina Technologies
 
Simple Scala DSLs
Simple Scala DSLs
linxbetter
 
So various polymorphism in Scala
So various polymorphism in Scala
b0ris_1
 
Q2 teenagers
Q2 teenagers
Brandon Hill
 
Scala’s implicits
Scala’s implicits
Pablo Francisco Pérez Hidalgo
 
Python in real world.
Python in real world.
[email protected]
 
Spark & Cassandra at DataStax Meetup on Jan 29, 2015
Spark & Cassandra at DataStax Meetup on Jan 29, 2015
Sameer Farooqui
 
Telco analytics at scale
Telco analytics at scale
datamantra
 
Real-World Scala Design Patterns
Real-World Scala Design Patterns
NLJUG
 
Introduction to Spark Training
Introduction to Spark Training
Spark Summit
 
Python programming - Everyday(ish) Examples
Python programming - Everyday(ish) Examples
Ashish Sharma
 
Neo, Titan & Cassandra
Neo, Titan & Cassandra
johnrjenson
 
Lets learn Python !
Lets learn Python !
Kiran Gangadharan
 
Scala Implicits - Not to be feared
Scala Implicits - Not to be feared
Derek Wyatt
 
df: Dataframe on Spark
df: Dataframe on Spark
Alpine Data
 
Apache spark with Machine learning
Apache spark with Machine learning
datamantra
 
Titan: Scaling Graphs and TinkerPop3
Titan: Scaling Graphs and TinkerPop3
Matthias Broecheler
 
Introduction to Spark 2.0 Dataset API
Introduction to Spark 2.0 Dataset API
datamantra
 
Apache Spark Core
Apache Spark Core
Girish Khanzode
 
Spark Summit EU 2016: The Next AMPLab: Real-time Intelligent Secure Execution
Spark Summit EU 2016: The Next AMPLab: Real-time Intelligent Secure Execution
Databricks
 
DataStax: Titan 1.0: Scalable real time and analytic graph queries
DataStax: Titan 1.0: Scalable real time and analytic graph queries
DataStax Academy
 
Spark Dataframe - Mr. Jyotiska
Spark Dataframe - Mr. Jyotiska
Sigmoid
 

More Related Content

Viewers also liked (18)

Scala’s implicits
Scala’s implicits
Pablo Francisco Pérez Hidalgo
 
Python in real world.
Python in real world.
[email protected]
 
Spark & Cassandra at DataStax Meetup on Jan 29, 2015
Spark & Cassandra at DataStax Meetup on Jan 29, 2015
Sameer Farooqui
 
Telco analytics at scale
Telco analytics at scale
datamantra
 
Real-World Scala Design Patterns
Real-World Scala Design Patterns
NLJUG
 
Introduction to Spark Training
Introduction to Spark Training
Spark Summit
 
Python programming - Everyday(ish) Examples
Python programming - Everyday(ish) Examples
Ashish Sharma
 
Neo, Titan & Cassandra
Neo, Titan & Cassandra
johnrjenson
 
Lets learn Python !
Lets learn Python !
Kiran Gangadharan
 
Scala Implicits - Not to be feared
Scala Implicits - Not to be feared
Derek Wyatt
 
df: Dataframe on Spark
df: Dataframe on Spark
Alpine Data
 
Apache spark with Machine learning
Apache spark with Machine learning
datamantra
 
Titan: Scaling Graphs and TinkerPop3
Titan: Scaling Graphs and TinkerPop3
Matthias Broecheler
 
Introduction to Spark 2.0 Dataset API
Introduction to Spark 2.0 Dataset API
datamantra
 
Apache Spark Core
Apache Spark Core
Girish Khanzode
 
Spark Summit EU 2016: The Next AMPLab: Real-time Intelligent Secure Execution
Spark Summit EU 2016: The Next AMPLab: Real-time Intelligent Secure Execution
Databricks
 
DataStax: Titan 1.0: Scalable real time and analytic graph queries
DataStax: Titan 1.0: Scalable real time and analytic graph queries
DataStax Academy
 
Spark Dataframe - Mr. Jyotiska
Spark Dataframe - Mr. Jyotiska
Sigmoid
 
Spark & Cassandra at DataStax Meetup on Jan 29, 2015
Spark & Cassandra at DataStax Meetup on Jan 29, 2015
Sameer Farooqui
 
Telco analytics at scale
Telco analytics at scale
datamantra
 
Real-World Scala Design Patterns
Real-World Scala Design Patterns
NLJUG
 
Introduction to Spark Training
Introduction to Spark Training
Spark Summit
 
Python programming - Everyday(ish) Examples
Python programming - Everyday(ish) Examples
Ashish Sharma
 
Neo, Titan & Cassandra
Neo, Titan & Cassandra
johnrjenson
 
Scala Implicits - Not to be feared
Scala Implicits - Not to be feared
Derek Wyatt
 
df: Dataframe on Spark
df: Dataframe on Spark
Alpine Data
 
Apache spark with Machine learning
Apache spark with Machine learning
datamantra
 
Titan: Scaling Graphs and TinkerPop3
Titan: Scaling Graphs and TinkerPop3
Matthias Broecheler
 
Introduction to Spark 2.0 Dataset API
Introduction to Spark 2.0 Dataset API
datamantra
 
Spark Summit EU 2016: The Next AMPLab: Real-time Intelligent Secure Execution
Spark Summit EU 2016: The Next AMPLab: Real-time Intelligent Secure Execution
Databricks
 
DataStax: Titan 1.0: Scalable real time and analytic graph queries
DataStax: Titan 1.0: Scalable real time and analytic graph queries
DataStax Academy
 
Spark Dataframe - Mr. Jyotiska
Spark Dataframe - Mr. Jyotiska
Sigmoid
 

Variance in scala

  • 1. VARIANCE IN SCALA LYLE KOPNICKY JUNE 16, 2015
  • 3. MUSIC COLLECTION class Album(val title: String, val tracks: Vector[Track]) class Track(val title: String, val length: Int)
  • 4. DIFFERENT KINDS OF ALBUMS class 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
  • 5. DIFFERENT KINDS OF TRACKS trait 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
  • 6. TYING TRACK SUBTYPES TO ALBUM SUBTYPES trait 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
  • 7. THIS IS ALLOWED val 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)
  • 8. THIS IS NOT ALLOWED val 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.MP3Track val ma = new MP3Album("Greatest Hits", Vector(vt1, vt2), 256) ^
  • 9. WHY DOES THIS WORK? 1. Albumis covariant with respect to each of its valfields 2. Vectoris covariant with respect to its type parameter
  • 10. WHAT'S COVARIANCE? A relationship between compound types: Given types , and some compound type , if , A <: B F[A] F[A] <: F[B] then we say that is covariant in .F A
  • 12. WHY IS THIS VALID? A value of a subtype can be used where a value of the supertype is expected The properties of a Vector are such that the subtyping relationship carries over from the parameter Vectors are statically declared to be covariant
  • 13. WHAT MAKES A SUBTYPE? Imagine a context where a value of type is expected If you can always use a value of type in that context, Then . B A A <: B
  • 14. WHO DECIDES WHETHER YOU CAN? Languages may define subtyping rules Without such a rule, there is no subtyping In statically-typed languages, subtype relationships must be declared
  • 15. WIDTH SUBTYPING OF RECORDS The 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.
  • 16. DEPTH SUBTYPING OF RECORDS The subtype has fields that are subtypes of the corresponding fields in the supertype: class TrackRating(val track: Track, val rating: Int) class VinylTrackRating(val track: VinylTrack, val rating: Int) extends TrackRating The extendsis required to statically declare the relationship.
  • 17. YOU CAN'T DO THIS class TrackRating(val ratedThing: Track, val rating: Int) class AlbumRating(val ratedThing: Album, val rating: Int) extends TrackRating Because Albumis not a subtype of Track.
  • 18. PROPERTIES OF A VECTOR Immutable Can add or modify elements to produce a new Vector
  • 19. VECTOR SUBTYPING If context expects a Vector[Track], it expects to be able get an element and treat it like a Track so it's OK if that's really a VinylTrack
  • 20. VECTOR SUBTYPING It also expects to be able to append a Vector[Track]: v1 : Vector[VinylTrack] v2 : Vector[Track] v1 ++ v2 : Vector[Track]
  • 21. VECTOR SUBTYPING Thus Vector[VinylTrack] Vector[Track]<: It's also declared as covariant in the type: Vector[+A]
  • 22. ALBUM SUBTYPING Combined 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
  • 24. ALBUM WITH A TYPE PARAMETER You could instead turn the Track type into a constrained parameter: 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 additional intermediate types, such as Album[VinylTrack].
  • 25. CONTRAVARIANCE The reverse of covariance If , then Commonly occurs in function parameters A <: B F[B] <: F[A]
  • 26. FUNCTION SUBTYPING type 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 <:
  • 27. FUNCTION SUBTYPING Context expects a Predicate[Album]. Can we provide a Predicate[VinylAlbum], such as spinsFast? No, because the context might apply it to an MP3Album, which has no rpmfield.
  • 28. FUNCTION SUBTYPING Context expects a Predicate[VinylAlbum]. Can we provide a Predicate[Album], such as isBigAlbum? Yes, because even an MP3Albumhas a number of tracks, as do all Albums.
  • 29. FUNCTION SUBTYPING So, when , Predicate[B] Predicate[A].A <: B <: We say that Predicateis contravariant in its type parameter. We should declare it as type Predicate[-A] = A -> Boolean
  • 31. VARIANCE IN FUNCTIONS In general, the function type is contravariant in and covariant in . A → B A B A type that is contravariant in one paramter and covariant in the other is called provariant. Map[-A, +B]is another example. For multiple input parameters, function types are contravariant in all parameter types.
  • 32. VARIANCE IN METHODS Methods are like functions, but also have a selftype. You might think of selfas a hidden parameter, and therefore contravariant. But methods are covariant in the selftype because it is used to dispatch to the appropriate subclass. In languages with multiple dispatch, all parameters used for dispatch are covariant.
  • 33. COVARIANT AND CONTRAVARIANT POSITIONS Every class/trait declaration has covariant and contravariant positions 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 position method arguments (player) are in contravariant position method return types are in covariant position
  • 34. COVARIANT AND CONTRAVARIANT POSITIONS So, 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
  • 35. COVARIANT AND CONTRAVARIANT POSITIONS When creating a subtype, the types in each position are checked against the supertype, to satisfy the variance rules. Type parameters must simultaneously meet the criteria for all positions where they are used.
  • 36. COVARIANT AND CONTRAVARIANT POSITIONS Try 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 a contravariant position
  • 37. INVARIANCE We can use the type parameter in both positions if we give up covariance: 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 a supertype of Album[Track].
  • 38. INVARIANCE But this is OK: trait Album { val title: String val tracks: Vector[Track] def isLongerThan(otherAlbum: Album): Boolean } VinylAlbum.isLongerThanmust also accept all Albums
  • 39. INVARIANCE All vartypes are invariant positions, since they serve as both the return type of a getter and the parameter type of a setter. trait Album { val title: String var tracks: Vector[Track] } This would make Albuminvariant in Vector[Track], so subtypes couldn't specialize.
  • 40. INVARIANCE Thinking 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 and contravariant positions, subtypes can't specialize.
  • 41. INVARIANCE OF MUTABLE TYPES Like a var, Array[T]must be invariant in T. Think of how it would go wrong if Arraywere covariant: val vt1: VinylTrack = ... val mta = Array.empty: Array[MP3Track] mta(0) = vt1 # BAD You can't use an Array[MP3Track]in place of an Array[Track].
  • 42. INVARIANCE OF MUTABLE TYPES Now think of how it would go wrong if Arraywere contravariant: val vt1: VinylTrack = ... val ta = Array(vt1): Array[Track] val mt = ta(0): MP3Track # BAD val p = mt.path You can't use an Array[Track]in place of an Array[MP3Track].
  • 43. KEY POINTS When using extends, component types are checked Those in covariant position must be subtypes Those in contravariant position must be supertypes Type parameters can be declared as covariant or contravariant These declarations will be checked against positions Parameters in both positions must be invariant
  • 44. OTHER TOPICS Nested levels of contravariance flipping each other (function of function) Binary methods Useful overloading