This is a recipe from the Scala Cookbook (2nd Edition). This recipe is titled, Working with Parameterized Traits in Scala.
Problem
As you become more advanced in working with Scala types, you want to write a trait whose methods can be applied to generic types, or limited to other specific types.
Solution
Depending on your needs you can use type parameters or type members with traits. This example shows what a generic trait type parameter looks like:
trait Stringify[A]:
def string(a: A): String
This example shows what a type member looks like:
trait Stringify:
type A
def string(a: A): String
Type parameter example
Here’s a complete type parameter example:
trait Stringify[A]:
def string(a: A): String = s"value: ${a.toString}"
@main def typeParameter =
object StringifyInt extends Stringify[Int]
println(StringifyInt.string(100))
Type member example
And here’s the same example written using a type member:
trait Stringify:
type A
def string(a: A): String
object StringifyInt extends Stringify:
type A = Int
def string(i: Int): String = s"value: ${i.toString}"
@main def typeMember =
println(StringifyInt.string(42))
TIP: The free book, The Type Astronaut’s Guide to Shapeless, by Dave Gurnell, shows an example where a type parameter and type member are used in combination to create something known as a dependent type.
Discussion
With the type parameter approach you can specify multiple types. For example, this is a Scala implementation of the Java Pair
interface that’s shown on this Oracle Generic Types page:
trait Pair[A, B]:
def getKey: A
def getValue: B
That demonstrates the use of two generic parameters in a small trait example.
An advantage of parameterizing traits using either technique is that you can prevent things from happening that should never happen. For instance, given this trait and class hierarchy:
sealed trait Dog
class LittleDog extends Dog
class BigDog extends Dog
you can define another trait with a type member like this:
trait Barker:
type D <: Dog //type member
def bark(d: D): Unit
Now you can define an object with a bark
method for little dogs:
object LittleBarker extends Barker:
type D = LittleDog
def bark(d: D) = println("wuf")
and you can define another object with a bark
method for big dogs:
object BigBarker extends Barker:
type D = BigDog
def bark(d: D) = println("WOOF!")
Now when you create these instances:
val terrier = LittleDog()
val husky = BigDog()
this code will compile:
LittleBarker.bark(terrier)
BigBarker.bark(husky)
and this code won’t compile, as desired:
// won’t work, compiler error
// BigBarker.bark(terrier)
This demonstrates how a Scala type member can declare a base type in the initial trait, and how more specific types can be applied in the traits, classes, and objects that extend that base type.
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |