Mike Slinn

Parametric Types

— Draft —

Published 2014-03-09. Last modified 2017-06-16.
Time to read: 9 minutes.

We already know that classes and methods can accept value parameters. This lecture introduces types as a second kind of parameter for traits, classes and methods. The time method introduced earlier is made parametric, and a DSL for C-style if-then-else syntax is developed.

Unlike value parameters, which are evaluated at runtime, type parameters are evaluated at compile time.

The sample code for this lecture can be found in courseNotes/src/main/scala/ParametricFun.scala.

First Look at Parametric Polymorphism

The hasValue method takes an Option of Int and returns an indication of whether the Option contains Some value or not.

Scala REPL
scala> def hasValue(option: Option[Int]): Boolean = option.isDefined
hasValue: (option: Option[Int])Boolean
scala>
hasValue(Some(3)) res0: Boolean = true
scala>
hasValue(None) res1: Boolean = false

There is nothing in the definition above that is specific to integers, other than the declaration of Option’s type.

The code for checking to see if an Option of String has a value is identical.

Scala REPL
scala> def hasValue(option: Option[String]): Boolean = option.isDefined
hasValue: (option: Option[String])Boolean
scala>
hasValue(Some("Hi")) res2: Boolean = true
scala>
hasValue(None) res3: Boolean = false

We can generalize the two definitions above by parameterizing not just the input option but also its type. Type parameters are enclosed within square braces and, if provided, must appear before value parameter lists.

Scala REPL
scala> def hasValue[T](option: Option[T]): Boolean = option.isDefined
hasValue: [T](option: Option[T])Boolean 

Type parameters are listed in square brackets after the method name and can be used in the type declarations within the method. In the definition above, T is the only type parameter, standing for any Scala type. This defines a family of hasValue functions, one for each type T that the Scala compiler notices is used in the program: hasValue[Int], hasValue[String], and so on.

Scala REPL
scala> hasValue[Int](Some(1))
res4: Boolean = true
scala>
hasValue[Int](None) res5: Boolean = false

Recall from the Option, Some and None lecture of the Introduction to Scala course that None is a singleton object, so there is only one instance. This means that unlike Some, the None instance of Option is identical for all parametric types.

Let’s look at a few more scenarios.

Scala REPL
scala> hasValue[String](Some("a"))
res6: Boolean = true
scala>
hasValue[Double](Some(123.4)) res7: Boolean = true
scala>
hasValue[(String, Int)](Some(("x",2))) res8: Boolean = true
scala>
hasValue[Array[Int]](Some(Array(1,2))) res9: Boolean = true

In most cases the explicit type annotation for hasValue can be dropped because the Scala compiler will usually be able to infer the correct parametric type automatically.

Scala REPL
scala> hasValue(Some(1))
res10: Boolean = true
scala>
hasValue(Some("a")) res11: Boolean = true
scala>
hasValue(Some((1, 2))) res12: Boolean = true
scala>
hasValue(Some(Array(1, 2))) res13: Boolean = true

The method hasValue exhibits parametric polymorphism (also known as generics or templates), which allows the method definition to represent many hasValue methods differing only for the type of the elements in the Option input.

If this version of hasValue was lifted to a Function using eta expansion, the equivalent function’s type would be Option[T] => Boolean. This means for any type T, hasValue takes an Option of type T and returns a Boolean. Notice that I have defined the equivalent Function with def, because neither a val nor a var may be parametric.

Scala REPL
scala> def hasValue[T] = (option: Option[T]) => option.isDefined
hasValue: [T]=> Option[T] => Boolean
scala>
hasValue(Some(1)) res14: Boolean = true

Scoped Parameters

Classes, traits and methods can be parametric. It is useful and accurate to think of Scala type parameters as being executed at compile time the same way as class and method arguments are executed at runtime. Consider the following: two classes are defined, Outer and Inner, each with a String property called propOuter and propInner, respectively.

Scala code
case class Outer(propOuter: String)
case class Inner(propInner: String)

Now we define a class, Test, which is parametric in T. This means that the compiler will evaluate the value of T at compile time and substitute values. The value of T is known throughout the class.

Scala code
class Test[T] {
  def print[U](u: U, t: T): Unit = println(s"Test.test: u=$u; t=$t")
}

Test also defines a method called print which is parametric in U. The value for U is local to the method. If type values for T and U are not explicitly provided then the compiler will attempt to infer them. We can now create an instance of Test and invoke print with the type Outer substituted as the value of T.

Scala REPL
scala> new Test[Outer].print(Inner("inner"), Outer("outer"))
Test.test: u=Inner(inner); t=Outer(outer) 

We did not explicitly provide a type value U, however the compiler was able to deduce that the type value must be Inner.

Shadowed Parameters

Just as with variables and method arguments, type names can shadow each other. As an example, consider the class Shadow, which is parametric in T. Notice that this class defines a method called print, which is also parametric in T. The method’s type T is distinct from the class type T and shadows it. In this example, the outer T type is defined but not used.

Scala REPL
scala> class Shadow[T] {
  def print[T](t: T): Unit = println(s"Test.test: y=$t")
}
defined class Shadow
scala> new Shadow[Outer].print(Inner("inner")) Test.test: y=Inner(inner)
scala> new Shadow[Inner].print(Inner("inner")) Test.test: y=Inner(inner)

You can run this code by typing.

Scala REPL
$ sbt "runMain ScopedParams"
Test.test: y=Inner(inner) 

Parametric Polymorphism

Here is the time method from the previous lecture, rewritten to use parametric types to return a strongly typed result. We’ll define it as part of a parametric class.

Scala code
class Timeable[T] {
  def time(block: => T): T = {
    val t0 = System.nanoTime()
    val result: T = block
    val elapsedMs = (System.nanoTime() - t0) / 1000000
    println("Elapsed time: " + elapsedMs + "ms")
    result
  }
}

The square brackets introduce a parametric type to the time method. In this case we only define one type, called T. The type could equally well have been called Result, but it is common to use one letter names for parametric types. Anywhere in the method that you wish to refer to that type, merely write T. You can invoke the method with any value of T, and if you do not specify the value of T it will be inferred. In other words, if you wanted to time three methods that return an Int, a String and a Double, this single parametric method saves you from having to write the following methods, because T is a parametric type.

Scala code
def time(block: => Int): Int = ???
def time(block: => String): String = ???
def time(block: => Double): Double = ???

Let’s use this method to again time how long it takes to compute Pi to many decimal places. A Function1[Int, Double] version of calculatePiFor is provided in multi/package.scala and could just as well be used.

Scala code
def calculatePiFor(decimalPlaces: Int): Double = {
var acc = 0.0
for (i <- 0 until decimalPlaces)
  acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1)
  acc
}

Now we can run time on calculatePiFor. Notice the the result is strongly typed as Double, instead of Any as was the case in the previous lecture.

Scala REPL
scala> new Timeable.time[Double] { calculatePiFor(100000) }
Elapsed time: 0ms
res10: Double = 3.1415826535897198 

Let’s time a function that returns a String instead of a Double.

Scala REPL
scala> new Timeable.time[String] { io.Source.fromURL("https://scalacourses.com").mkString.trim }
Elapsed time: 481ms
res11: String =
"<!DOCTYPE html>
<html lang="en">
<head>
<title>Welcome to ScalaCourses.com</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src=’/webjars/jquery/1.9.1/jquery.min.js’ type=’text/javascript’>
<script src=’/webjars/jquery-ui/1.10.2/ui/minified/jquery-ui.min.js’ type=’text/javascript’>
<script src=’/webjars/bootstrap/2.3.2/js/bootstrap.min.js’ type=’text/javascript’>
<script src=’/webjars/bootstrap-datepicker/1.0.1/js/bootstrap-datepicker.js’ type=’text/javascript’>
<script src=’/assets/javascripts/jwerty.js’ type="text/javascript">
<script src=’/javascriptRoutes’ type="text/javascript">
<link href=’/assets/images/favicon.ico’ rel="shortcut icon" type="image/x-ic... 

Scala’s type inference means that we did not have to write the type parameters, so this would also work.

Scala code
new Timeable.time { calculatePiFor(100000) }
new Timeable.time { io.Source.fromURL("https://scalacourses.com").mkString.trim }

You can run this program by typing.

Scala REPL
sbt "runMain TimeableT" 

’With’ Pattern Revisited

We can make a parametric polymorphic version of the With pattern we learned in the More Fun With Functions lecture of the Introduction to Scala course. For such a powerful facility, it is surprising how simple and easy it is to define and use with Scala.

Scala code
def withT[T, U](t: T)(operation: T => U): U = operation(t)

Now let’s use it:

Scala REPL
scala> withT(6) { i => println(i*2) }
12
scala>
withT(new java.util.Date) { println } Tue Oct 29 05:48:07 PDT 2013
scala>
case class Blarg(i: Int, s: String) defined class Blarg scala> withT(Blarg(1, "hi")) { blarg => println(blarg) } Blarg(1,hi)

You can run this code example by typing:

Shell
$ sbt "runMain ParametricWith"

We will extend the With pattern in the Parametric ’With’ Pattern Revisited section of the Partial Functions lecture, and further enhance it in the Structural Types With Parametrics section of the Structural Types lecture so that it uses duck typing to manage resources.

Exercise – What is Output?

Scala code
package parametricSimulation {
  abstract class AbstractSimulation[X, Y, Z] {
    def simulate(x: X, y: Y, z: Z): String
  }
object AbstractSimulation { implicit lazy val defaultSimulation = new AbstractSimulation[Int, Double, String] { def simulate(x: Int, y: Double, z: String) = s"Companion simulation: $x, $y, $z" } }
trait Implicit { implicit lazy val defaultSimulation = new AbstractSimulation[Int, Double, String] { def simulate(x: Int, y: Double, z: String) = s"Implicit simulation: $x, $y, $z" } }
object ParametricSimulation extends App { object CompanionSimulation { println(implicitly[AbstractSimulation[Int, Double, String]].simulate(1, 2, "three")) }
object TraitSimulation extends Implicit { println(implicitly[AbstractSimulation[Int, Double, String]].simulate(10, 20, "thirty")) }
CompanionSimulation TraitSimulation } }

Solution

Lets walk through the program.

  1. All of the code is contained within a package statement, which defines the contents of the parametricSimulation package.
  2. The AbstractSimulation abstract class is parametric in types X, Y and Z, and declares but does not define the simulate method, which accepts instances of X, Y and Z, and returns a String.
  3. The AbstractSimulation companion object defines an implicit lazy val of type AbstractSimulation called defaultSimulation. Because this implicit is defined in a companion object it has a lower priority than other implicits. Because the implicit is lazy it is not instantiated unless it is referenced.
  4. The Implicit trait defines another implicit lazy val of type AbstractSimulation, also called defaultSimulation. This is a way to define an implicit within the scope of the trait, or any type that the trait is mixed into. Mixing this trait into a class, object or trait will cause this implicit to be used instead of the implicit in the AbstractSimulation companion object.
  5. The console application called ParametricSimulation defines two objects, and then evaluates them. Both of these objects have similar side effects: they obtain a reference to the active AbstractSimulation implicit in scope, invoke the simulate method, and print out the returned value.

You can run ParametricSimulation like this.

Shell
$ sbt "runMain parametricSimulation.ParametricSimulation"
Companion simulation: 1, 2.0, three
  Implicit simulation: 10, 20.0, thirty 

Example: Ternary Operator DSL

Let’s define a DSL that provides a ternary operator similar to the ? operator in C and C++. The syntax we will implement is.

Scala code
predicate ? thenClause | elseClause

We don’t want thenClause to be evaluated unless the predicate evaluates true, and we don’t want the elseClause to be evaluated unless the predicate evaluates false.

Here is an example of desired usage.

Scala code
util.Random.nextBoolean ? "Yes" | "No"

If we rewrite the expression using dot notation it will be easier to understand how to define the ternary operator for Scala.

Scala code
util.Random.nextBoolean.?("Yes").|("No")

Our approach will be.

  1. Define a class called TernaryThen that holds the predicate.
  2. Define a method within TernaryThen called ? that accepts the then clause without evaluating it and returns the evaluation result if required.
  3. Define an inner class called TernaryEval within the TernaryThen class that accepts the as-yet-not-evaluated then expression.
    1. Define a method within TernaryEval called | that the user chains from TernaryThen after writing the then clause and passes in the else clause.
    2. The | method will evaluate the predicate and return the result of evaluating either the then clause or the else clause, as appropriate.
  4. To trigger the DSL we will need an implicit conversion from Boolean (the predicate) to TernaryThen. Any time the compiler encounters a Boolean expression and the implicit conversion is in scope, the compiler will attempt to create an instance of TernaryThen. If successful, the DSL will be executed at runtime, otherwise the Boolean expression will remain as-is. For the conversion to TernaryThen to succeed, the Boolean must be chained to an invocation of the ? method and the | method.

All of the parameters should be defined as being call by name, so they are not evaluated until their value is required. This means that neither the TernaryEval class nor the TernaryThen class can be defined as value objects.

Here is the code. Even though it is short, there is a lot going on.

class TernaryThen(predicate: => Boolean) {
import scala.language.implicitConversions
def ?[A](thenClause: => A) = new TernaryEval(thenClause)
class TernaryEval[A](thenClause: => A) { def |(elseClause: => A): A = if (predicate) thenClause else elseClause } }
implicit def toTernaryThen(predicate: => Boolean): TernaryThen = new TernaryThen(predicate)
Once the AST is built, the type of the expression is known even though the expression has not yet been evaluated

This code is worthy of further explanation.

  • The toTernaryThen implicit conversion attempts to convert all instances of Boolean to TernaryThen.
  • The TernaryThen ? method accepts thenClause without evaluating it. TernaryThen’s ? method is parametric in A, which is the method’s return type. Note that A’s scope is limited to the ? method because the compiler can only infer the type of the then clause when it parses the expression. The key to understanding this is to realize that the Scala compiler builds an abstract syntax tree (AST) when it parses an expression; the AST can then optionally evaluate the expression at runtime if required. Once the compiler builds the AST, the type of the expression is known at compile time even though the expression has not yet been evaluated at runtime.
  • The ? method creates an instance of an inner class called TernaryEval and passes in the thenClause reference, which still has not been evaluated.
  • The TernaryEval inner class is parametric in A, and accepts the reference to the then clause, and defines the | method. The then clause returns an instance of A. Note that A’s scope is limited to TernaryEval. Although this A is distinct from the ? method’s return type (which is also called A) both A types will be the same because they are both defined to be the result of evaluating thenClause.
  • The | method accepts the elseClause parameter without evaluating it and returns an instance of A.
  • The | method finally evaluates predicate, and returns the result of evaluating thenClause or elseClause. The result is of type A, and each A must resolve to the same type.

Let’s try it.

Scala REPL
scala> util.Random.nextBoolean ? "Yes" | "No"
res6: String = Yes

This usage example shows how the type of the else clause must match the type of the then clause. This works because Predef provides an implicit conversion from Int to Double.

Scala REPL
scala> util.Random.nextBoolean ? 1.0 | 99
res5: Double = 1.0

Since there is no implicit conversion of Double to Int, this fails.

Scala REPL
scala> util.Random.nextBoolean ? 1 | 99.0
<console>:14: error: type mismatch;
 found   : Double(99.0)
 required: Int
       util.Random.nextBoolean ? 1 | 99.0

^

You can run this code by typing.

Shell
$ sbt "runMain Ternary1"
util.Random.nextBoolean ? "Yes" | "No" = No

Another implementation

Here is an implementation that uses anonymous classes.

implicit def toTernaryThenAC(predicate: Boolean) = new {
def ?[A](thenClause: => A) = new {
def |(elseClause: => A) = if (predicate) thenClause else elseClause
}
}

Let’s try it.

Scala REPL
scala> util.Random.nextBoolean ? "Yes" | "No"
res8: String = Yes

The anonymous class implementation defines two new classes each time the Scala compiler resolves the implicit conversion. It is therefore better to use the TernaryThen implementation.

You can run this code by typing.

Shell
$ sbt "runMain Ternary2"
util.Random.nextBoolean ? "Yes" | "No" = No

Parametric Traits as Rich Interfaces

This pattern is useful to enhance existing Scala and Java classes. It is an alternative to the Enrich My Library pattern introduced in the Implicit Classes lecture. This approach has the advantage of not introducing implicits, which can be hard to debug. Take a moment and study this trait.

trait RichIterable[A] {
def iterator: java.util.Iterator[A]
def foreach(f: A => Unit): Unit = { val iter = iterator while (iter.hasNext) f(iter.next) }
def foldLeft(seed: A)(f: (A, A) => A): A = { var result = seed foreach { item => result = f(result, item) } result } }

As you can see, the iterator method is abstract. In other words, iterator is declared but not defined. Concrete subclasses of the RichIterable trait must implement the iterator method.

Does this statement in the foreach method seem odd.

val iter = iterator

This statement captures the result of the iterator method and stores it in the locally scoped variable called iter. This is done because iterators are often fragile. Saving the result of a method call that returns an iterator is good defensive programming.

Now let’s consider how to use the RichIterable trait. Remember the declaration of iter is java.util.Iterator[A]. If you examine the Javadoc for java.util.HashSet, you will see that it already defines a method called iterator that returns a java.util.Iterator<A> (that was written in Java - you would write it in Scala as java.util.Iterator[A]). Consider the following anonymous class.

java.util.HashSet[Int] with RichIterable[Int]

The resulting anonymouis class is concrete because the superclass (HashSet) provides an implementation for the abstract RichInterable trait’s undefined iterator method. An instance could be created by writing.

val richSet = new java.util.HashSet[Int] with RichIterable[Int]

Let’s use RichIterable to add Scala functionality to an anonymous subclass of java.util.HashSet, like this.

Scala REPL
scala> val richSet = new java.util.HashSet[Int] with RichIterable[Int]
richSet: java.util.HashSet[Int] with RichIterable[Int] = []
scala> richSet.add(1) res13: Boolean = true
scala> richSet.add(2) res14: Boolean = true
scala> richSet.add(2) res15: Boolean = true
scala> val total = richSet.foldLeft(0)(_ + _) total: Int = 6
scala> s"richSet = ${richSet.toArray.mkString(", ")}; total = $total" richSet = 1, 2, 3; total = 6

To run this program, type.

Shell
$ sbt "runMain RichInterface"

@implicitAmbiguous

Scala 2.12 introduced the @implicitAmbiguous annotation, which can only be applied to implicit methods that accept type parameters. From the Scala 2.12-M3 release notes.

The @implicitAmbiguous annotation allows customizing the error message when an implicit search finds multiple ambiguous values. Refer to the Scaladoc of the annotation class for an example.

The code example in the Scaladoc is rather crazy. Here is a simpler example.

Scala REPL
scala> case class X[C, D](c: C, d: D)
defined class X
scala> @annotation.implicitAmbiguous("ambig1 problem: X[${J}, ${J}]") implicit def ambig1[J](j: J): X[J, J] = X(j, j) ambig1: [J](j: J)X[J,J]
scala> @annotation.implicitAmbiguous("ambig2 problem: X[${J}, ${J}]") implicit def ambig2[J](j: J): X[J, J] = X(j, j) ambig2: [J](j: J)X[J,J]
scala> val x12: X[Int, Int] = 1 <console>:15: error: ambig1 problem: X[Int, Int] val x12: X[Int, Int] = 1 ^

Parameters with Abstract Classes vs Traits

You can define a trait with parameters.

trait Blah[X, Y] { /* ...
*/ }

And you can define an abstract class with parameters.

abstract class Blah[X, Y] { /* ...
*/ }

I generally recommend using abstract classes with parameters instead of traits with abstract members that must be overriden by implementing classes. Abstract classes usually works better with type inference, because subclasses do not infer their type arguments or the types of their methods from methods of superclasses, but subclasses do infer the their type arguments from their parameters, which superclasses can pass in.


* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.