Răzvan Petruescu bio photo

Răzvan Petruescu

Functional programming for the masses

Twitter LinkedIn Stackoverflow BitBucket Coursera

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 ApplicationEvents and ErrorEvents in the same way, the duplication can be removed considering that the two events are sub-types of SystemEvent:

def systemEventFired[E <: SystemEvent](e: E, sink: Sink[SystemEvent]): Unit = {
  // do some processing related to the event
  // ......
  // notify the event sink
  sink.notify(e)
}

// ApplicationEvent is a subtype of SystemEvent
systemEventFired(new ApplicationEvent {}, ses)

// ErrorEvent is a subtype of SystemEvent
systemEventFired(new ErrorEvent {}, ges)

An useful heuristic for understanding the <: symbol is:

The selected type `E` must be equal to or a subtype of the `upper bound type`

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:

def filterOutErrorEvents[E >: ApplicationEvent](e: E, src: Source[SystemEvent]): Unit = {
  // the source might also emit an error event, but it is not certain
  src.get()
}

A useful heuristic for understanding the >: symbol is:

The selected type `E` must be equal to or a supertype of the `lower bound type`

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:

filterOutErrorEvents(new SystemEvent {}, syes)

As a reminder, the type of syes is

trait SystemEventSource extends Source[SystemEvent]

Obviously, the method compiles even if you pass it a Source[ErrorEvent]:

trait ErrorEventSource extends Source[ErrorEvent]

private val ees = new ErrorEventSource {
  override def get(): ErrorEvent = ???
}

filterOutErrorEvents(new SystemEvent {}, ees)

Context bounds

A context bound has the syntax T : M.

An useful heuristic for understanding the syntax is:

`M` is another parameterized type, and an implicit value of type `M[T]` must
exist in the scope

Now there’s a good time to go back to this article.

The example:

def asAggregate[T: AggregateAdapter](t: T) = implicitly[AggregateAdapter[T]].toAggregate(t)

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:

`AggregateAdapter` is another parameterized type, and an implicit value of type
`AggregateAdapter[T]` should exist in the scope.

The following lines create a Document and pass it to the asAggregate method, which will take care of the conversion.

val d = new Document {}

asAggregate(d).size

The heuristic in this case:

When calling the method with a `Document` parameter, an implicit value of type
`AggregateAdapter[Document]` should exist in the scope

The Manifest Context Bound

This presentation would not be complete without mentioning the manifest context bound.

Syntax: T : Manifest.

Manifests 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, Manifests 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.

def threeTimesPackedIntoArray[T <: Event : Manifest](e: T): Array[T] = {
  val a = new Array[T](3)
  a(0) = e; a(1) = e; a(2) = e
  a
}

threeTimesPackedIntoArray(new SystemEvent {})

threeTimesPackedIntoArray(new ErrorEvent {})

Using the aforementioned heuristic(s), we have:

The selected type `T` must be equal to or a subtype of `Event`, and an implicit
value of type `Manifest[T]` must exist in the scope

That Manifest[T] is generated by the compiler, when needed, with all the known information for that type at that time.