Reputation: 37164
Here is an observation I've had from a chapter in Programming Scala.
In Scala, I often see the abstract trait pattern:
trait Abstract {
type T
def transform(x: T): T
val initial: T
var current: T
}
class Concrete extends Abstract {
type T = String
def transform(x: String) = x + x
val initial = "hi"
var current = initial
}
Why would I choose the abstract trait pattern over parameterized generics?
trait Abstract[T] {
def transform(x: T): T
val initial: T
var current: T
}
class Concrete extends Abstract[String]{
def transform(x: String): x + x
val initial: "hi"
var current: initial
}
Upvotes: 2
Views: 75
Reputation: 17431
The two approaches are mostly equivalent. One reason we might prefer a type member is so that methods can be written with dependent types rather than having to be generic:
def doSomethingWith(a: Abstract): a.T = ...
is arguably more readable than
def doSomethingWith[T](a: Abstract[T]): T = ...
at least as the signatures get more complicated (particularly if we're doing type-level programming, using Abstract
as a type-level function).
There might be also implications for type inference; I don't know precisely how scala type inference works, but as far as I can tell there's no way to partially specify the types of a function:
def functionWeWantToCall[U, V, W](a: Abstract[U], b: Abstract[V], c: Abstract[W])
functionWeWantToCall[String, _, _](x, y, z) //won't compile
def functionWeWantToCall(a: Abstract, b: Abstract, c: Abstract)
functionWeWantToCall(x: Abstract{type T = String}, y, z) //works
So that's a reason I've sometimes found myself using the type member approach.
Also, of course, some people (e.g. those from an ML background) simply find the type member approach more familiar or readable.
Upvotes: 3