This text comes from the 2nd Edition of the Scala Cookbook.
Scala Problem
You want to create a Scala class (and associated methods) that uses a simple generic type.
Solution
As a library writer, you’ll define generic types when declaring your classes. For instance, here’s a small linked-list class that’s written so that you can add new elements to it. It’s mutable in that way, like an ArrayBuffer
:
class LinkedList[A]:
private class Node[A] (elem: A):
var next: Node[A] = _
override def toString = elem.toString
private var head: Node[A] = _
def add(elem: A): Unit =
val n = new Node(elem)
n.next = head
head = n
private def printNodes(n: Node[A]): Unit =
if n != null then
println(n)
printNodes(n.next)
def printAll() = printNodes(head)
Notice how the generic type A
is sprinkled throughout the class definition. This generic type is a placeholder for actual types like Int
and String
, which the user of your class can specify.
For example, to create a list of integers with this class, first create an instance of it, declaring the type that it will contain to be the type Int
:
val ints = LinkedList[Int]()
Then populate it with Int
values:
ints.add(1)
ints.add(2)
Because the class uses a generic type, you can also create a LinkedList
of type String
:
val strings = LinkedList[String]()
strings.add("Emily")
strings.add("Hannah")
strings.printAll()
Or any other type you want to use:
val doubles = LinkedList[Double]()
doubles.add(1.1)
doubles.add(2.2)
This demonstrates the basic use of a generic type when creating a class.
Discussion
When you use a “simple” generic parameter like A
when defining a class, you can also define methods inside and outside of that class that use exactly that type. To explain what this means, start with this type hierarchy:
trait Person { def name: String }
class Employee(val name: String) extends Person
class StoreEmployee(name: String) extends Employee(name)
You might use this type hierarchy when modeling a point-of-sales application for a pizza store chain, where a StoreEmployee
is someone who works at a store location. (You might then also have an OfficeEmployee
type for people who work in the corporate office.)
Note: I omitted an image here that is shown in the Scala Cookbook.
Given this type hierarchy, you can create a method to print a LinkedList[Employee]
, like this:
def printEmps(es: LinkedList[Employee]) = es.printAll()
Now you can give printEmps
a LinkedList[Employee]
, and it will work as desired:
// works
val emps = LinkedList[Employee]()
emps.add(Employee("Al"))
printEmps(emps)
So far, so good; this works as desired.
The limits of this approach
Where this “simple” approach doesn’t work is if you try to give printEmps
a LinkedList[StoreEmployee]()
:
val storeEmps = LinkedList[StoreEmployee]()
storeEmps.add(StoreEmployee("Fred"))
// this line won’t compile
printEmps(storeEmps)
This is the error you get when you try to write that code:
printEmps(storeEmps)
^^^^^^^^^
Found: (storeEmps : LinkedList[StoreEmployee])
Required: LinkedList[Employee]
The last line won’t compile because:
-
printEmps
expects aLinkedList[Employee]
. -
storeEmps
is aLinkedList[StoreEmployee]
. -
LinkedList
elements are mutable. -
If the compiler allowed this,
printEmps
could add plain oldEmployee
elements to theStoreEmployee
elements instoreEmps
. This can’t be allowed.
As discussed in other solutions in the Scala Cookbook, the problem here is that when a generic parameter is declared as A
in a class like LinkedList
, that parameter is invariant, which means that the type is not allowed to vary when used in methods like printEmps
. (A detailed solution to this problem is shown in the Scala Cookbook.)
Type parameter symbols
If a class requires more than one type parameter, use the symbols shown in Standard symbols for generic type parameters in Scala. For instance, in the official Java Generics documentation, Oracle shows an interface named Pair
, which takes two types:
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
You can port that interface to a Scala trait, as follows:
trait Pair[K, V]:
def getKey: K
def getValue: V
If you were to take this further and implement the body of a Pair
class (or trait), the type parameters K
and V
would be spread throughout your class, just as the symbol A
was used in the LinkedList
example.
>Note: I generally prefer using the symbols A
and B
for the first two generic type declarations in a class, >but in a case like this where the types clearly refer to key and value — such as in a Map
class — >I prefer K
and V
. But use whatever makes sense to you.
The same Oracle document lists the Java type parameter naming conventions. These are similar in Scala, except that Java starts naming simple type parameters with the letter T
, and then uses the characters U
and V
for subsequent types. The Scala standard is that the first type should be declared as A
, the next with B
, and so on, as shown in Standard symbols for generic type parameters in Scala.
Table 1. Standard symbols for generic type parameters in Scala
Symbol | Description |
---|---|
|
Refers to a simple type, such as |
|
Used for the 2nd, 3rd, 4th types, etc. For example: class List[A]: def map[B](f: A => B): List[B] = ??? |
|
Typically refers to a key in a Java map. (I also prefer |
|
Refers to a numeric value. |
|
Typically refers to a value in a Java map. (I also prefer |
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
See Also
-
Oracle’s Java “Generic Types” documentation.
-
You can find a little more information on Scala’s generic type naming conventions at the Scala Style Guide’s Naming Conventions page.