|
Re: The Origins of Scala
|
Posted: May 5, 2009 12:10 PM
|
|
Hi Andrew,
> > I'm curious what the implication of assuming covariance > > for type parameter is in practice? > > Eiffel allows covariant input parameters for methods. > This approach is often "beaten up" by academic > c researchers, but it always seemed to be intuitive to > me. > I looked up Bertrand Meyer's rationale in his book Object-Oriented Software Construction. I have the 2nd edition from 1997. The reason is I was reading some older posts on this subject on Artima and found an old blog by Bruce Eckel that quoted him:
http://www.artima.com/weblogs/viewpost.jsp?thread=132041
What Meyers shows is a hierarchy with a class Skier with two subclasses, Girl and Boy. (It's a high school ski team going to a meet.) Skier has a method share that takes another skier, and is used to model which skier will share a room with which other skier during the event. So in Java it might look like:
abstract class Skier {
abstract void share(other: Skier);
}
Then he wants to model a rule that only girls can share a room with girls, and boys can share a room with boys. So he says what you want to do in the subclasses is:
class Boy extends Skier {
void share(other: Boy) { ... }
}
class Girl extends Skier {
void share(other: Girl) { ... }
}
In Eiffel I think the subclass share methods override (actually implement, since it is abstract) the share method in Skier, because this kind of covariance of parameter types is allowed. In Java, the subclass share methods would overload the superclass one, and you'd be told that you still need to implement share(Skier) in the subclasses.
Meyers then later says, in a section entitled "polymorphic perversity", that "a harmful scheme is easy to make up," and gives this example (again translated to Java):
Skier s;
Boy b = new Boy;
Girl g = new Girl;
s = b;
s.share(g);
Sorry about the the one-char names, but that's what Meyer used. Basically, you assign a Boy to a variable of type Skier. You can then call share on that variable and pass in any Skier, because that's the parameter type of share in class Skier. Well, a Girl is a Skier, so I could pass a Girl to share a room with a Boy. Now what Meyer could have concluded about all this is "Cats and dogs living together! Mass Hysteria!" However, his take was:
With contravariance one would not have these problems: as you specialize the target of a call (s in this example), you would generalize the argument. Contravariance, as a result, leads to simpler mathematical models of the inheritance-redefinition-polymorphism mechanism. For that reason a number of theoretical articles have advocated contravariance. But the argument is not very convincing, since, as we have seen and as the literature readily admits, contravariance is of essentially no practical use.
An argument often encountered in the programming literature is that one should strive for techniques that have simple mathematical models. Mathematical elegance, however, is one one of several design criteria; we should not forget to make our designs realistic and useful too. In computing science as in other disciplines, it is after all much easier to device drastically simple theories if we neglect to make them agree with reality.
So rather than trying to force a covariant body into a contravariant suit, we should accept the reality for what it is, covariant, and study ways to remove the unpleasant effects.
What I think Meyer means when talking about reality is that you want to actually model that two kinds of Skiers are Boys and Girls, and that Skiers can share rooms, but only Boys can share with other Boys, and Girls with other Girls. So he suggests that the "rare but serious possibility of type violations when [covariance is] combined with polymorphism," as he put it, are something that can be managed and are worth getting the benefit of being able to model this kind of reality. He never said it was about covariance being simpler or easier to understand, just that he want to be able to model things like making sure Girl and Boy skiers stay in separate rooms.
So fast forward 12 years, and here's how you could do this in Scala that keeps both the types and the high schoolers out of trouble. First, in Skier you add an abstract type as a member:
abstract class Skier { type SuitableRoommate <: Skier def share(other: SuitableRoommate) }
The <: means that type SuitableRoommate must be a subtype of Skier. The SuitableRoommate type is abstract, because there's no equals sign specifying it to be some other type. The share method is also abstract, as in the Java example I showed previously, because it has no definition. In Scala, you don't need to put the keyword abstract on these as in Java.
OK, now in the subclasses, you define what the SuitableRoommate type is:
I edited this later, because I had some bugs in the code below. Fixed now.
class Boy extends Skier { type SuitableRoommate = Boy def share(other: Boy) {} }
class Girl extends Skier { type SuitableRoommate = Girl def share(other: Girl) {} }
For Boys, a suitable roommate is another Boy. And for Girls, a suitable roommate is another Girl. Now you can put Boy and Girl in the method signatures, and you get the behavior you want:
scala> :load skier.scala Loading skier.scala... defined class Skier defined class Boy defined class Girl
scala> val s: Skier = new Boy s: Skier = Boy@101afe
scala> val g = new Girl g: Girl = Girl@cf63bb
scala> s.share(g) <console>:10: error: type mismatch; found : Girl required: s.SuitableRoommate s.share(g) ^
|
|