I was reading A Tour of Scala: Abstract Types. When is it better to use abstract types?
For example,
abstract class Buffer {
type T
val element: T
}
rather that generics, for example,
abstract class Buffer[T] {
val element: T
}
I was reading A Tour of Scala: Abstract Types. When is it better to use abstract types?
For example,
abstract class Buffer {
type T
val element: T
}
rather that generics, for example,
abstract class Buffer[T] {
val element: T
}
I had the same question when I was reading about Scala.
The advantage to using generics is that you are creating a family of types. Nobody will need to subclass
Buffer
—they can just useBuffer[Any]
,Buffer[String]
, etc.If you use an abstract type, then people will be forced to create a subclass. People will need classes like
AnyBuffer
,StringBuffer
, etc.You need to decide which is better for your particular need.
You may use abstract types in conjunction with type parameters to establish custom templates.
Let's assume you need to establish a pattern with three connected traits:
in the way that arguments mentioned in type parameters are AA,BB,CC itself respectfully
You may come with some kind of code:
which would not work in this simple way because of type parameter bonds. You need to made it covariant to inherit correctly
This one sample would compile but it sets strong requirements on variance rules and can not be used in some occasions
The compiler will object with bunch of variance check errors
In that case you may gather all type requirements in additional trait and parametrize other traits over it
Now we can write concrete representation for the described pattern, define left and join methods in all classes and get right and double for free
So, both abstract types and type parameters are used for creating abstractions. They both have weak and strong point. Abstract types are more specific and capable to describe any type structure but is verbose and require to explicit specified. Type parameters can create bunch of types instantly but gives you additional worry about inheritance and type bounds.
They give synergy to each other and can be used in conjunction for creating complex abstractions that can't be expressed with only one of them.
You have a good point of view on this issue here:
The Purpose of Scala's Type System
A Conversation with Martin Odersky, Part III
by Bill Venners and Frank Sommers (May 18, 2009)
Update (October2009): what follows below has actually been illustrated in this new article by Bill Venners:
Abstract Type Members versus Generic Type Parameters in Scala (see summary at the end)
(Here is the relevant extract of the first interview, May 2009, emphasis mine)
General principle
There have always been two notions of abstraction:
In Java you also have both, but it depends on what you are abstracting over.
In Java you have abstract methods, but you can't pass a method as a parameter.
You don't have abstract fields, but you can pass a value as a parameter.
And similarly you don't have abstract type members, but you can specify a type as a parameter.
So in Java you also have all three of these, but there's a distinction about what abstraction principle you can use for what kinds of things. And you could argue that this distinction is fairly arbitrary.
The Scala Way
We decided to have the same construction principles for all three sorts of members.
So you can have abstract fields as well as value parameters.
You can pass methods (or "functions") as parameters, or you can abstract over them.
You can specify types as parameters, or you can abstract over them.
And what we get conceptually is that we can model one in terms of the other. At least in principle, we can express every sort of parameterization as a form of object-oriented abstraction. So in a sense you could say Scala is a more orthogonal and complete language.
Why?
What, in particular, abstract types buy you is a nice treatment for these covariance problems we talked about before.
One standard problem, which has been around for a long time, is the problem of animals and foods.
The puzzle was to have a class
Animal
with a method,eat
, which eats some food.The problem is if we subclass Animal and have a class such as Cow, then they would eat only Grass and not arbitrary food. A Cow couldn't eat a Fish, for instance.
What you want is to be able to say that a Cow has an eat method that eats only Grass and not other things.
Actually, you can't do that in Java because it turns out you can construct unsound situations, like the problem of assigning a Fruit to an Apple variable that I talked about earlier.
The answer is that you add an abstract type into the Animal class.
You say, my new Animal class has a type of
SuitableFood
, which I don't know.So it's an abstract type. You don't give an implementation of the type. Then you have an
eat
method that eats onlySuitableFood
.And then in the
Cow
class I would say, OK, I have a Cow, which extends classAnimal
, and forCow type SuitableFood equals Grass
.So abstract types provide this notion of a type in a superclass that I don't know, which I then fill in later in subclasses with something I do know.
Same with parameterization?
Indeed you can. You could parameterize class Animal with the kind of food it eats.
But in practice, when you do that with many different things, it leads to an explosion of parameters, and usually, what's more, in bounds of parameters.
At the 1998 ECOOP, Kim Bruce, Phil Wadler, and I had a paper where we showed that as you increase the number of things you don't know, the typical program will grow quadratically.
So there are very good reasons not to do parameters, but to have these abstract members, because they don't give you this quadratic blow up.
thatismatt asks in the comments:
I am not sure the relationship is that different between using abstract types or generics. What is different is:
To understand what Martin is speaking about when it comes to "explosion of parameters, and usually, what's more, in bounds of parameters", and its subsequent quadratically growth when abstract type are modeled using generics, you can consider the paper "Scalable Component Abstraction" written by... Martin Odersky, and Matthias Zenger for OOPSLA 2005, referenced in the publications of the project Palcom (finished in 2007).
Relevant extracts
Definition
(Note: Family polymorphism has been proposed for object-oriented languages as a solution to supporting reusable yet type-safe mutually recursive classes.
A key idea of family polymorphism is the notion of families, which are used to group mutually recursive classes)
bounded type abstraction
(Note, from Peter Canning, William Cook, Walter Hill, Walter Olthoff paper:
Bounded quantification was introduced by Cardelli and Wegner as a means of typing functions that operate uniformly over all subtypes of a given type.
They defined a simple "object" model and used bounded quantification to type-check functions that make sense on all objects having a specified set of "attributes".
A more realistic presentation of object-oriented languages would allow objects that are elements of recursively-defined types.
In this context, bounded quantification no longer serves its intended purpose. It is easy to find functions that makes sense on all objects having a specified set of methods, but which cannot be typed in the Cardelli-Wegner system.
To provide a basis for typed polymorphic functions in object-oriented languages, we introduce F-bounded quantification)
Two faces of the same coins
There are two principal forms of abstraction in programming languages:
The first form is typical for functional languages, whereas the second form is typically used in object-oriented languages.
Traditionally, Java supports parameterization for values, and member abstraction for operations. The more recent Java 5.0 with generics supports parameterization also for types.
The arguments for including generics in Scala are two-fold:
First, the encoding into abstract types is not that straightforward to do by hand. Besides the loss in conciseness, there is also the problem of accidental name conflicts between abstract type names that emulate type parameters.
Second, generics and abstract types usually serve distinct roles in Scala programs.
The latter arises in particular in two situations:
In a system with bounded polymorphism, rewriting abstract type into generics might entail a quadratic expansion of type bounds.
Update October 2009
Abstract Type Members versus Generic Type Parameters in Scala (Bill Venners)
(emphasis mine)
Example: