On Sunday, 27 May 2018 at 06:00:30 UTC, IntegratedDimensions
wrote:
[…] This is a potential suggestion for including such a feature
in the D language to provide sightly more consistency.
Solving this in the general case requires explicitly allowing,
specifying, and tracking covariance and contravariance relations
throughout the type system.
Object-oriented programming in the usual sense only models arrows
in one direction – covariance of return types (and possibly
contravariant argument types). As far as I'm aware, there is
little more useful to be done without making both compile-time
and run-time representations (i.e., type system and memory
layout) significantly more complex.
The only problem where it can leak is when we treat an cat as
an animal then put in dog food in to the animal, which is valid
when cat as treated as an animal, then cast back to cat. […]
But I've already pointed out two things about this: 1. The
hierarchy is maintained to be non-leaky at runtime(I never down
cast). […]
Even without explicit downcasts, there are still implicit upcasts
and covariant `this` pointers. Consider this:
---
class Food;
class Catfood : Food;
class Animal { Food f; void eat() { /+ eat f +/ } }
class Cat : Animal { Catfood : Food f; override void eat() { /+
eat f +/ } }
Animal a = new Cat;
a.f = new Food;
a.eat(); // calls Cat.eat()
---
As per your problem statement, given a (this) pointer of type Cat
– such as in Cat.eat() –, you'd presumably want `f` to be of type
Catfood. However, as shown, this is incompatible with Cat being a
subtype of Animal.
Haskell can handle these situations just fine.
Haskell also (mostly) lacks subtyping and mutable data.
–––
In general, this is a fun problem to think about – at least if
one does not expect to (quickly) come up with generic solutions.
Given your Haskell background, you might enjoy having a look at
what Scala and others have done with generics for an illustration
of what makes this tricky in an OOP environment.
As for D, I'd recommend taking a closer look at what your actual
design requirements are. If you don't require the full
flexibility of arbitrary co-/contravariance relations on an
unrestricted set of types, it is likely that you can brew your
own subtyping system with template magic to provide exactly the
required structure and behaviour. Template application is
invariant, so you have full freedom to allow precisely those
conversions you want to exist. The implementation will involve
structs that internally cast unrelated pointers, but that can be
hidden from the interface. You can always package this up as a
library if you succeed in making it generally useful.
— David