News for hackers. Stuff that matters.
Re:Covariance and Contravariance: a fresh look at an old iss... by continuational on Tuesday May 17, 2016 @04:59PM (#11717364)
Is there really enough value in subtyping to keep it around?

Sure, lots of things in the real world are naively "is-a" relationships, eg.

  interface Fruit { 
    boolean isSoft();
  }

  class Apple implements Fruit {
    boolean isSoft() { ... }
  }

  class Banana implements Fruit {
    boolean isSoft() { ... }
  }
But this is both less explicit and less flexible than modelling it as a "has-a" relationship, eg.

  interface Fruit { 
    boolean isSoft();
  }

  class Apple {
    Fruit asFruit() { ... }
  }

  class Banana {
    Fruit asFruit() { ... }
  }
The latter example needs no subtyping, and thus no covariance and no contravariance. All for the price of an explicit .asFruit() here and there instead of an implicit upcast.
Re:Covariance and Contravariance: a fresh look at an old iss... by catnaroek on Tuesday May 17, 2016 @06:48PM (#11718252)
Inheritance isn't the same thing as subtyping:

(0) Inheritance is a (rather undisciplined) form of code reuse - it's literally automation for copying and pasting part of an existing definition into the body of another. It doesn't presuppose a notion of type.

(1) Subtyping is a semantic relationship between two types: all terms of a subtype also inhabit its supertype(s).

There's nothing too wrong with inheritance as long as you're aware that it doesn't always lead to the creation of subtypes. This is, for example, the case in OCaml.

Sadly, Java, C# and C++ confuse matters by conflating classes with types (which is tolerable) and subclasses with subtypes (which is a logical absurdity and leads to painful workarounds, I mean, design patterns, as we all have learnt the hard way).

Re:Covariance and Contravariance: a fresh look at an old iss... by continuational on Wednesday May 18, 2016 @03:12AM (#11720299)
The Java style (Nominal) subtyping is what most people are familiar with, and the most common reason why people think subtyping is necessary, so let's not stray into other kinds of subtyping until we can agree on this kind.
Re:Covariance and Contravariance: a fresh look at an old iss... by catnaroek on Wednesday May 18, 2016 @10:25AM (#11722165)
I never said subtyping isn't necessary, and if you read my reply to btilly, you'd see that I actually suggested otherwise: subtyping is basic, natural and necessary, so languages should do it right.

Also, as I again previously said, nominal typing and even nominal subtyping are fine (well, I said “tolerable”, since they have downsides for modularity, but that's a topic for another day), but conflating inheritance with subtyping is a problem. To put it in Java terms, a subclass should only be considered a subtype if:

(0) The subclass doesn't override any methods that aren't abstract in the superclass. A subclass can do whatever its implementor wishes, but a subtype can't behave differently from a supertype.

(1) The subclass doesn't directly mutate any inherited fields from the superclass - this destroys inherited invariants. OTOH, reading inherited fields is just fine in a subtype.

In other words, a subclass is a subtype if and only if the type-checker has enough information to tell that the Liskov substitution principle actually holds.

Re:Covariance and Contravariance: a fresh look at an old iss... by btilly on Tuesday May 17, 2016 @06:24PM (#11718066)
This can be summed up with, Favor composition over inheritance. :-)

Indeed it is a good idea to use composition whenever feasible. But your problems aren't over. Suppose you write a method that can accept anything that implements the Fruit interface. You've got covariance again. Suppose you have a dictionary whose values are of type Apple. You can pass those values into that method. That's contravariance again. And so it goes.

It doesn't matter whether you're defining types by classes, or what interfaces you implement. You will have types of some sort, and as soon as you do, you have covariance and contravariance as concepts again.

Re:Covariance and Contravariance: a fresh look at an old iss... by continuational on Wednesday May 18, 2016 @02:40AM (#11720200)
You can't have co/contravariance without subtyping. Remove the extends and the implements keyword from Java, and you're rid of it. You can still create instances of Fruit using annonymous classes.
Re:Covariance and Contravariance: a fresh look at an old iss... by jayd16 on Tuesday May 17, 2016 @05:24PM (#11717569)
Simple things like adding a list of Apples to a list of Fruits become non-trivial.
Re:Covariance and Contravariance: a fresh look at an old iss... by continuational on Tuesday May 17, 2016 @06:08PM (#11717925)
Non-trivial? In Scala it would be:

    fruits ++= apples.map(_.asFruit)
Re:Covariance and Contravariance: a fresh look at an old iss... by shiro on Tuesday May 17, 2016 @07:35PM (#11718515)
That part is fine. The difficulty arises when you want to take out an apple from a list of fruits.
Re:Covariance and Contravariance: a fresh look at an old iss... by catnaroek on Tuesday May 17, 2016 @09:48PM (#11719156)
Why should you be able to do that?
Re:Covariance and Contravariance: a fresh look at an old iss... by shiro on Tuesday May 17, 2016 @11:31PM (#11719524)
Right, if you take type-safety first, you shouldn't be able to. But a list of abstract fruits has very limited usage without the ability to accessing concrete fruit instance. The adoption of downcasting in some OO languages came out from such needs, given that they lacked generic and/or algebraic types. And with that regard, I thought your solution didn't address the original covariance/contravariance problem (that is, want to have heterogeneous list of fruits and allowing to access concrete types of individual elements).

There are type-safe ways, like making a fruit a sum type of apple and banana, or using traits or type classes, etc. But is-a/has-a discussion seems a bit off from that.

Re:Covariance and Contravariance: a fresh look at an old iss... by catnaroek on Wednesday May 18, 2016 @10:59AM (#11722424)
> I thought your solution didn't address

FWIW, I didn't propose the original Scala snippet, so it isn't really “my” solution.

> the original covariance/contravariance problem

The problem is precisely preventing what you're trying to do, because it's unsafe.

Re:Covariance and Contravariance: a fresh look at an old iss... by shiro on Wednesday May 18, 2016 @12:51PM (#11723320)
Ah, ok, I mixed up you with continuational.

For this context, what I'm saying is that if you don't need to extract an apple from a list of fruits, then you can do it safely with inheritance, so whether using inheritance or delegation is irrelevant. Isn't it?

Re:Covariance and Contravariance: a fresh look at an old iss... by catnaroek on Wednesday May 18, 2016 @03:30PM (#11725036)
If you want to extract an apple from a list of fruits, you need runtime type information (so that you can test whether a particular fruit object happens to be an apple), which has nothing to do with whether your language has inheritance or not. Using runtime type information is a symptom of bad design, though, since it lessens the extent to which you can reason about programs (as pieces of text) by just looking at their types.
Re:Covariance and Contravariance: a fresh look at an old iss... by shiro on Thursday May 19, 2016 @06:22AM (#11729152)
I agree with you all. All I say is that suggesting delegation in place of inheritance to address contravariance issue seems off the mark.