> yes, it's easy to make up a scenario where parameter
> covariance leads to runtime holes. whether it is a major
> problem in practice is another question.
>
Yes, I think its the same question with respect to covariance of arrays in Java. It is type unsound, but relatively rare that people get ArrayStoreExceptions in practice. They do get them occasionally, though.
> > contravariance is of essentially no practical use.
>
> I'd agree with bertrand here. I've found contravariance
> of input parameters to be useless. covariance is
> statically unsound, and therefore the java position of
> invariance seems sensible in a record type model.
> (Obviously contravariance of return types is a different
> t story)
>
I turned out to need contravariance in ScalaTest matchers design, but it is really also an example of function parameters being contravariant, because in ScalaTest a matcher is a function. Users don't really need to worry about it, or understand why it is contravariant. They just know that things they try that they think should work turn out to work as expected. If you're interested, I put some info on it in this page (search for variance):
http://www.artima.com/scalatest/doc-0.9.5/org/scalatest/matchers/Matcher.html > > 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.
>
> we just spend so much time trying to form the perfect type
> system that I wonder if it would be better to accept
> occasional holes for simplicity and clarity. It's the old
> 80/20 rule. To me Bertrand's crazy example sort of makes
> sense. You have a boy skier and a girl skier and they
> can't share a room. However, if you just tell someone you
> have 2 skiers and to put them in a room together, and you
> do it, someone's going to get in trouble ;-)
>
I'd put the question slightly differently. Thinking about how to form the perfect type system is what academics are paid to do, and that's a worthy endeavor. Martin has come up with a pretty nice type system in Scala. The question I'd ask is whether the benefit offered by the type system is worth the cost of learning, understanding, and dealing with that type system. Also, what are the real benefits, and what are the real costs in practice?
> fair enough. i find it simpler then to understand from a
> modeling perspective. for instance, if you have a Java
> class animal:
>
> class Animal { void eat(Food f) {}}
>
> and you want to subclass it to make Bear, you'd ideally
> want:
>
> class Bear extends Animal { void eat (Honey h) {} }
>
> where eat is overridden and Honey is a subclass of Food.
> To me this makes modeling sense, even though we can then
> n ask the question as to what happens if we feed a bear
> weetabix...
>
That's funny. In Programming in Scala we use this example, and show that covariant method parameters would lead to the abhorrent notion that you could feed fish to cows!
> yes, you do it the same sort of way in Beta with virtual
> patterns. In Beta you still have type holes because the
> Boy and Girl would still be subclasses of Skier.
>
> So, in your scala example, does it mean that Boy and Girl
> are no longer subclasses of skier? if they are, what
> happens if you call Skier::share() on a boy as a Skier and
> pass in a girl as a Skier?
>
Oops. I wondered why I got the wrong compiler error. My example was full of bugs. I forgot to make Boy and Girl extend Skier, and forgot to put an explicit type on s. Here's what I meant:
abstract class Skier {
type SuitableRoommate <: Skier
def share(other: SuitableRoommate)
}
class Boy extends Skier {
type SuitableRoommate = Boy
override def share(other: Boy) {}
}
class Girl extends Skier {
type SuitableRoommate = Girl
override def share(other: Girl) {}
}
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)
^
> So, the idea of putting the share() function in the base
> class must be so that Boy and Girl can be treated
> polymorphically from the perspective of the share() method
> at some point, surely? otherwise, why wouldn't you just
> omit it from the Skier base and just place share(Girl) on
> the Girl class and vice versa?
>
Yes, that's the point. Share is still in there on the base class.
> And if Boy and Girl are no longer substitutable for Skier,
> then haven't we lost something major?
>
> Alternatively, i'm not understanding how Scala handles
> this.
>
Scala has something called path-dependent types, which means the actual type depends on the path you take to get to it. It highlights that objects actually do have members that are types. The type error is that they got a Girl where an s.SuitableRoommate was needed. The Scala compiler somehow keeps track that the object referenced by s, its type member is a Boy. So when you try to pass a Girl in there, you get the type error.