Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Scala Generics

0.00/5 (No votes)
5 Nov 2015 1  
So continuing on from the Scala for .NET series of posts. This time we will look at using Generics in Scala.

So continuing on from the Scala for .NET series of posts. This time we will look at using Generics in Scala.

The basic usage for generics is not that far removed from usage in .NET, where in Scala you may have generic methods/classes.

Generic Methods

Here is a simple example of a generic method

object ClassesDemo {
 
  def genericPrinter[A](theStuff : A) : Unit = {
    System.out.println(s"theStuff =$theStuff")
  }
 
  def main(args: Array[String]) =
  {
    genericPrinter("A String")
    genericPrinter(12)
    genericPrinter(Some(12L))
    System.in.read()
    ()
  }
 
}

Which when run will give the following results:

image

Generic Classes

It is also possible to create generic classes. Here is an example of creating a generic class, and its usage

class printer[A](theStuff : A) {
  def printIt() : Unit = {
    System.out.println(s"theStuff =$theStuff")
  }
}
 
 
object ClassesDemo {
 
  def main(args: Array[String]) =
  {
    new printer[String]("A String").printIt()
    new printer[Int](12).printIt()
    new printer[Tuple2[String,Int]]("A String",12).printIt()
    System.in.read()
    ()
  }
}

Which when run will give the following results:

image

View Bounds

In .NET we have generic constraints that we can apply such as this

public class MyGenericClass<t> where T : IComparable
{
 
}

In Scala this is accomplished by using “View Bounds”

A view bound was a mechanism introduced in Scala to enable the use of some type A as if it were some type B.

The typical syntax is this:

def f[A <% B](a: A) = a.bMethod 

In other words, A should have an implicit conversion to B available, so that one can call B methods on an object of type A. The most common usage of view bounds in the standard library (before Scala 2.8.0, anyway), is with Ordered, like this:

def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b

Because one can convert A into an Ordered[A], and because Ordered[A] defines the method <(other: A): Boolean, I can use the expression a < b.

Taken from http://docs.scala-lang.org/tutorials/FAQ/context-and-view-bounds.html up on date 05/11/15

Context Bounds

Context bounds were introduced in Scala 2.8.0, and are typically used with the so-called type class pattern, a pattern of code that emulates the functionality provided by Haskell type classes, though in a more verbose manner.

While a view bound can be used with simple types (for example, A <% String), a context bound requires a parameterized type, such as Ordered[A] above, but unlike String.

A context bound describes an implicit value, instead of view bound’s implicit conversion. It is used to declare that for some type A, there is an implicit value of type B[A] available. The syntax goes like this:

def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A] 

This is more confusing than the view bound because it is not immediately clear how to use it. The common example of usage in Scala is this:

def f[A : ClassManifest](n: Int) = new Array[A](n)

An Array initialization on a parameterized type requires a ClassManifest to be available, for arcane reasons related to type erasure and the non-erasure nature of arrays.

Another very common example in the library is a bit more complex:

def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b) 

Here, implicitly is used to retrive the implicit value we want, one of type Ordering[A], which class defines the method compare(a: A, b: A): Int.

Taken from http://docs.scala-lang.org/tutorials/FAQ/context-and-view-bounds.html up on date 05/11/15

Type Erasure

Unlike .NET generics are not baked into the JVM, as they are in .NET where they are actually part of the CLR.

Scala’s types are erased at compile time. This means that if you were to inspect the runtime type of some instance, you might not have access to all type information that the Scala compiler has available at compile time.

Like scala.reflect.Manifest, TypeTags can be thought of as objects which carry along all type information available at compile time, to runtime. For example, TypeTag[T] encapsulates the runtime type representation of some compile-time type T. Note however, that TypeTags should be considered to be a richer replacement of the pre-2.10 notion of a Manifest, that are additionally fully integrated with Scala reflection.

ClassTag / TypeTag / Manifest

These 3 classes are the most useful ones to use to maintain type information.

Let’s consider this bit of code:

import MyExtensions._
import scala.reflect.runtime.universe._
import scala.reflect._
 
object ClassesDemo {
 
 
  def genericMeth[A](xs: List[A]) = xs match {
    case _: List[String] => "list of strings"
    case _: List[Foo] => "list of foos"
  }
 
  def main(args: Array[String]) =
  {
    val x = System.out.print(genericMeth(List("string")))
 
    System.in.read()
    ()
  }
}

Which when compiled will give the following errors:

image

To solve this problem Manifests were introduced to Scala. But they have the problem not being able to represent a lot of useful types.

TypeTag(s)/ClassTag(s) are the preferred mechanism to use. Here is the above code written again use a TypeTag, this time the code compiles fine

import MyExtensions._
import scala.reflect.runtime.universe._
import scala.reflect._
 
object ClassesDemo {
 
  def genericMeth[A : TypeTag](xs: List[A]) = typeOf[A] match {
    case t if t =:= typeOf[String] => "list of strings"
    case t if t <:< typeOf[Foo] => "list of foos"
  }
 
  def main(args: Array[String]) =
  {
    val x =
    System.out.print(genericMeth(List("string")))
 
 
    System.in.read()
    ()
  }
}

Another thing I have personally found of use is to use TypeTag/ClassTag to help me create an instance of the correct type.

For example:

import scala.reflect.runtime.universe._
import scala.reflect._
 
trait Logger {
  def log() : Unit
}
 
class LoggerA() extends Logger {
  override def log(): Unit = {
    println("LoggerA.log() called")
  }
}
 
object ClassesDemo {
 
  def doTheLoggingClassTag[T <: Logger]()(implicit tag: scala.reflect.ClassTag[T]) = {
 
      val theObject = tag.runtimeClass.newInstance.asInstanceOf[T]
      theObject.log()
      println(s"theObject $theObject")
      ()
    }
 
  def main(args: Array[String]) =
  {
    doTheLoggingClassTag[LoggerA]()
 
    System.in.read()
    ()
  }
}

Which will give this output:

image

There are some great sources of information on this, here are a couple of links:

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here