The first parts (one two three ) of the series describe the concept of variance.
Part one is an useful read as it gives a good introduction to types and prepares the field.
All mechanisms presented here offer insights on how to apply constraints to the type parameters.
Type bounds
Looking back to part three
of the variance series of articles, the two methods, errorEventFired
and
appEventFired
are certainly in need of some improvements.
Assuming that both of these methods handle ApplicationEvent
s and ErrorEvent
s
in the same way, the duplication can be removed considering that the two events
are sub-types of SystemEvent
:
An useful heuristic for understanding the <:
symbol is:
The mechanism that allows this kind of constraint setting, made available by
Scala’s type system is called type bound
.
This kind of bound is called an upper bound
and the syntax is
T <: UpperBoundType
.
Scala also supports lower bounds
, and, unsurprisingly, the syntax is
T >: LowerBoundType
.
Consider the following contrived example, a method that receives an event and an event source and filters out the events that are errors:
A useful heuristic for understanding the >:
symbol is:
In this scenario, the e
param is constrained to be only of type
ApplicationEvent
and SystemEvent
but not of type ErrorEvent
!
The src
param is constrained to be only of type Source[SystemEvent]
,
Source[ApplicationEvent]
and Source[ErrorEvent]
. This is a direct
consequence of covariance.
The following call compiles just fine:
As a reminder, the type of syes
is
Obviously, the method compiles even if you pass it a Source[ErrorEvent]
:
Context bounds
A context bound has the syntax T : M
.
An useful heuristic for understanding the syntax is:
Now there’s a good time to go back to this article.
The example:
is presented as ‘alternative syntax’ since I did not want to introduce another concept (bounds) while dealing with type classes.
Applied to this example, the heuristic is:
The following lines create a Document
and pass it to the asAggregate
method,
which will take care of the conversion.
The heuristic in this case:
The Manifest Context Bound
This presentation would not be complete without mentioning the manifest context bound.
Syntax: T : Manifest
.
Manifest
s were added to Scala especially for arrays and were generalized to be
useful in other situations where type information must be available at runtime.
In other words, Manifest
s are a way to achieve reification
on the JVM.
A Scala Array
is a parameterized class. For example, an array of integers has
the type Array[Int]
. So code can be written for generic Array[T]
types, but
because on the JVM there are different types of arrays for every primitive type
and for objects (for example int[]
, short[]
or String[]
), a way to retain
this specialized type information was required so that generic array
implementations would know what underlying type is required.
To instantiate a generic Array[T]
, a Manifest[T]
object is needed.
Using the aforementioned heuristic(s), we have:
That Manifest[T]
is generated by the compiler, when needed, with all the
known information for that type at that time.