But the fact that the following is unsound (and has been for a while) will always remain a mystery to me:
public class Main {
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
public static void main (String[] args) {
Main x = new Main();
Dog[] doges = new Dog[10];
x.t(doges);
}
public void t(Animal[] bar) {
bar[0] = new Cat();
bar[0] = new Animal(); // also breaks
}
}
The array type seems completely tractable and may be able to be caught by the compiler.This isn't an error, it was a design decision: https://en.wikipedia.org/wiki/Covariance_and_contravariance_...
You could call it bad design, but fixing it would (imo) just make Java even worse than it already is.
Man goes to doctor,
and says:
"Gee, doc, it
really hurts
when I do this."
Doctor replies:
"Then don't do that."Horrified, the second looks around and spots a civil engineer. Engineer shrugs and says, "Don't do that."
;).
There is a deeper discussion here; if you are writing good automatic tests anyway, which I hope you are, then of how much value is a rigorious type system? :)
> of how much value is a rigorous type system?
It saves a lot of time on those tests you won't have to write and maintain just to ensure that you're getting your types correct. Strong static types are also good documentation. Some type systems (e.g. Haskell) can even explicitly indicate whether a procedure is pure or has effects. Static types also enable dispatch that can be resolved at compile-time, resulting in more efficient execution. And the list goes on.
Of course, advocates of languages without static types have heard and dismissed all these arguments before. I guess it's really just a matter of taste.
For the record, I personally think a language like Typescript hit a sweet spot. Not a lot of mandatory ceremony but you have the choice to add types where it matters, e.g. at interface level. Also the duck typing saves you from inventing class names for all kind of almost-primitive types.
"Don't write tests. Generate them."
Cats and dogs being mammals is a taxonomy. It's data, not code. Unfortunately, I see OO programmers coding taxonomies all the time just for the sake of it, ending up with deep inheritance trees of classes without ever taking the step back and asking themselves, why they do it the first place. (e.g. to share functionality or to reuse code)
But then I don't know anything about type system theory.
You could force every array-function-caller to cast their args to e.g. `Object[]`, but that seems like it'd require more work for even less safety. Though I'd love a read-only concept in Java, for read purposes :|
[1]: I think. If I'm wrong, someone please correct me!
Edit: Could use a better word for "good" that sounds less dismissive, but I couldn't think of one. Java's generics are lacking in certain areas, unfortunately.
I'd like to see examples in languages where this is possible, if there are any?
Passing collections of subtypes where a read-only collection of supertypes is expected by the receiving code is fine, because the receiving code can't do anything that would break type safety.
But Java decided to only have a single array type, as a matter of expediency. But they also wanted covariance, because, as you point out, sometimes it's just convenient - more expediency. And so the trade-off was to enforce safety at runtime.
It's still a defect in a sense that the design itself could be made sound, if there were a read-only array reference type in Java.
And I didn't mean to imply that array references should always be read-only. What I meant is that there should be a way to say "the referenced array cannot be mutated through this reference", like const* in C. 90% of the time, when you pass or return arrays, this is exactly what you want.
Just because D isa B it does not mean list(D) isa list(B).
listContaining(Dog) isa listContaining(Animal)
listAccepting(Animal) isa listAccepting(Dog)(apologies for the bad terminology, I'm not too familiar with the details)
basically. the same concept applies in a number of areas, with varying support (e.g. Java doesn't support contravariance in its method overloading: http://stackoverflow.com/a/2501513/16295 )
I'm curious what line of thinking could prompt this question. Not all trade-offs are good choices just because they're trade-offs! Maybe it has something to do with the recent spate of language design choices justified with "it was a trade-off", as if the words "trade-off" is an incantation that puts a technical decision above criticism.
Indeed. But they're not automatically bad choices either. Are you saying this one is a bad trade-off?
What you really want in that instance is to be able to easily and explicitly cast the Dog[] to Animal[] before calling the method. A better language would make this easy instead. I think Java's Vector type can do it, so it surprises me that they allow that sort of type weirdness with arrays..
Can a language could resolve this issue without generics or some other contract feature (which Java did later add), or without limiting the scope of array definitions and their use?
May be good to note that Array<Dog> doesn't actually extend Array<Animal>, so there shouldn't be any polymorphic usage between them, and the return statement and method call scenarios are compile time errors with proper generics.
Arrays aren't generic types like ArrayList. Arrays are core language constructs parameterized by their element types. Array types are not erased in Java.
You can’t pass Dog[] where Animal[] is expected, it’s one of the more interesting properties of the Array, because its the only case where that doesn’t work.
For the exact reason you mentioned.
That is, you're agreeing, not disagreeing.
To see why this is broken, consider a method that takes an array of Animal and sets the first element to be a Cat instance. Java's type system will allow you to call this method with an array of Dog which will blow up at runtime on the assignment to the array element.
I noticed this immediately when I first started using Java (version 1.0.4!) as I had just read the classic 1985 OO type systems paper by Cardelli. I didn't have an OO background and was surprised that a "modern" language like Java would have this flaw in its type system given that the problem was well known years before.