4 ms·
Very interesting, thanks. I also ran up against another annoying bug/oversight/v0.1ism : You can't inherit from a generic class without becoming generic yourse
by archgrove 12y ago
Very interesting, thanks.
I also ran up against another annoying bug/oversight/v0.1ism : You can't inherit from a generic class without becoming generic yourself. So, even if you fully instantiate your parent's type information, you have to be generic as well! For example:
class Foo<T> { ... }
class Bar : Foo<String> { ... }
You'd expect Bar to be a non-generic type, but this throws a compiler error. You have to declare:
class Foo<T> { ... }
class Bar<String> : Foo<String> { ... }
Which seems just odd. The best workaround I've found thus far is
class Foo<T> { ... }
class BarClass<String> : Foo<String> { ... }
typealias Bar = BarClass<String>
Bleh. I've filed a radar against this one - it seems like an annoying oversight, even for this early version.
- jkrems 12y agoWell, derived classes are meant to be drop-in replacement for the base class. In that sense it totally makes sense that you can't have a non-generic child class of a generic base class. Otherwise it encourages design where classes are treated as method dumps.
- bjeanes 12y agoExactly what I was going to say. This seems like a good design decision, especially because the typealias hack offers a work around if you really need to.
- barrkel 12y agoDon't mix up your polymorphism. The fact that an instance of A is assignment compatible with a location of type B does not imply that a class of type A is assignment compatible with a location of type class of B. This is not the case for languages like C#, Java, C++ etc. (despite those languages not having class types); if a subclass does not define all the constructors of its ancestor class, the subclass is not a "drop-in replacement". Delphi does have class value polymorphism that mirrors instance polymorphism, and it can be a source of confusion, not to mention type holes. For A inheriting from B, you can construct a value of type A using a constructor of B if you assign A to a location of type class of B; A's constructor won't run, and its assumptions and invariants won't necessarily hold. It's one of several problems that makes designing robust classes in Delphi awkward.
- twic 12y agoNo. If the child class binds the type parameter of the base class (as in this example), then it's meaningless to think of that class as being generic. In this example, Bar is always Bar<String> - you can't have a Bar<Integer> or a Bar<anything else>. So why not just call it Bar? FWIW, this is how it works in Java, and it seems to work. Indeed, calling it Bar<String> seems really weird to me. Usually, in a declaration, the thing inside the angle brackets is a the name of a parameter, which can assume various values at runtime: class Foo<T> But in the subclass, it's the name of a type: class Bar<String> Essentially, one is a declaration, and the other is an expression. Like and l-value and an r-value. I don't think i've ever seen a language which allows both of those in the same place.
- jkrems 12y agoI don't think Java is a good example for OOP best practice. For exactly these reasons. I can't think of a reason this should be allowed. It screams "I'm trying to misuse class hierarchy to do specialization instead of using composition".
- pietro 12y agoNot classes; instances. So instances of Foo : Bar<T> should be drop-in replacements for instances of Bar<T>.
- hamstergene 12y agoI think you (and @twic in discussion below) misunderstand what's going on here. `class Bar<String> : Foo<String>` is exactly the same as `class Bar<T> : Foo<T>`. The identifier `String` in declaration of `Bar` does not refer to built-in type String, it is a tag for any type. In other words, you CAN have `Bar<Int>`, `Bar<Float>` and `Bar<everything else>` after using that declaration of `Bar`.
- specialist 12y agoIn Java, to clean up the brackets (exposed via APIs) I'll do something like: class NodeList extends List<Node> { ... }