digitalmars.D - General problem I'm having in D with the type system

• IntegratedDimensions (87/87) May 26 2018 (see my other most recent post for disclaimer)
• Vijay Nayar (18/26) May 26 2018 On Sunday, 27 May 2018 at 06:00:30 UTC, IntegratedDimensions
• JN (8/12) May 27 2018 I think the problem is in your hierarchy. If Animal can have
• Basile B. (51/139) May 27 2018 1/ I think that signatures could solve this problem in a nice way
• David Nadlinger (46/55) May 27 2018 Solving this in the general case requires explicitly allowing,
IntegratedDimensions <IntegratedDimensions gmail.com> writes:
```(see my other most recent post for disclaimer)

My designs generally work like this:

Main Type   uses   Subservient types
A                      a
B                      b
C                      c

where C : B : A, c : b : a.

In the usage I must also keep consistent the use of c in C, b in
B, a in A. There is little if nothing in the type system that
allows me to specify and force this type of relationship.
Although there is some covariant functions that do help such as
overriding properties with covariance.

class a;
class b : a;

class A { a x;  property a X() { return x; }  property void X(a
v) { x = v; } }
class _B { b x;  property b X() { return x; }  property void X(a
v) { x = v; } }
class B : A {  property b X() { return cast(b)x; }  property void
X(b v) { x = v; } }

Note that class _B is basically that of A which changes the type
a in to a b but otherwise is identical. This is to prove a point
of relationship in that _B uses b just as B should.

Class B tries to treat x as a type b as much as possible. IN
fact, by design, x is always assigned a b.

In this case, the design is safe by effort rather than type
consistency.

A f = new B();

f.x is a type of b which is of type a, so no violations here.

B g = new B();

g.x is of b type of so no violations.

but note that

f.x and g.x both accept type a. Of course g.X enforces type
safety.

Effectively the subservient types always grow with the main types
in parallel so they never get out of step. In category theory
this is equivalent to a natural transformation.

A -> a
|    |
v    v
B -> b

Ideally one simply should express this in a meaningful way:

class B : A
{
extend b : a x;
property b X() { x; } // note that we do not need a cast
property void X(b v) { x = v; }
}

the syntax tells the compile that x of type a in A is of type b
in B and that b must extend a. This then gives us something more
proper to _B.

Note that now

B g = new B();

g.x = new a(); // is invalid g.x is forced to be b which is
derived from a(or anything derived from b)

These designs are very useful because they allow a type and all
it's dependencies to be extended together in a "parallel" and
keep type consistency. Natural transformations are extremely
important in specify structural integrity between related types.
While D allows this using "hacks"(well, in fact I have yet to get
the setter to properly work but it is not necessary because of
direct setting and avoiding the property setter).

This is a potential suggestion for including such a feature in
the D language to provide sightly more consistency.

Here is a "real world"(yeah, right!) example:

class food;
class catfood;

class animal { food f; }
class cat : animal { catfood : food f; }

animal  ->   food
|            |
v            v
cat     ->   catfood

Of course, I'm not sure how to avoid the problem in D of

animal a = new cat();

a.f = new food()
auto c = cast(cat)a;

as now f in cat will be food rather than catfood.

The cast may have to be applied for the subservient types too
internally. (which the compiler can do internally) but should the
main object be set to null or just the subservient object?

auto c = cast(cat)a; // if cast(cat) is extended should c be null
or just c.f? The first case is safer but harder to fix and is the
nuke option. In this case one might require two casting methods

auto c1 = cast(cat)a; // c1 is null
auto c2 = ncast(cat)a; // c2 is not null, c2.f is null

Thoughts, ideas?
```
May 26 2018
```On Sunday, 27 May 2018 at 06:00:30 UTC, IntegratedDimensions
wrote:

The problem description is not very clear, but the catfood
example gives a bit more to work with.

animal  ->   food
|            |
v            v
cat     ->   catfood

Of course, I'm not sure how to avoid the problem in D of

animal a = new cat();

a.f = new food()
auto c = cast(cat)a;

Cast operations are generally not guaranteed to preserve type
safety and should be avoided when possible.  But if I understand
your description, you have the following relations and
transitions:

animal owns food
cat    owns catfood
animal may be treated as a cat (hence the casting)
food may be treated as a catfood (hence the casting)

It may be that the inheritance relationship is backwards in your
use case.  If "animal" may be treated as a "cat", then the
inheritance should be other other way around, and "animal" would
inherit from "cat".

What specific kinds of relationships are you trying to model
among what kinds of entities?
```
May 26 2018
IntegratedDimensions <IntegratedDimensions gmail.com> writes:
```On Sunday, 27 May 2018 at 06:59:43 UTC, Vijay Nayar wrote:
On Sunday, 27 May 2018 at 06:00:30 UTC, IntegratedDimensions
wrote:

The problem description is not very clear, but the catfood
example gives a bit more to work with.

animal  ->   food
|            |
v            v
cat     ->   catfood

Of course, I'm not sure how to avoid the problem in D of

animal a = new cat();

a.f = new food()
auto c = cast(cat)a;

Cast operations are generally not guaranteed to preserve type
safety and should be avoided when possible.  But if I
understand your description, you have the following relations
and transitions:

animal owns food
cat    owns catfood
animal may be treated as a cat (hence the casting)
food may be treated as a catfood (hence the casting)

It may be that the inheritance relationship is backwards in
your use case.  If "animal" may be treated as a "cat", then the
inheritance should be other other way around, and "animal"
would inherit from "cat".

No, this would make no sense. Inheritance is about
specialization, taking a type and specifying more constraints or
properties to make it more well defined or more specific. Or,
simply, a superset.

What specific kinds of relationships are you trying to model
among what kinds of entities?

I've already mentioned this. It is natural for specializations of
a type to also specialize dependencies. The animal/cat example is
valid.

A animal can be any thing that is an animal that eats any food.
a cat is an animal that is a cat and eats only food that cats eat.

This is true, is it not? cats may eat dog food, it is true, but
cats do not eat any animal food. They do specialize. Cat food may
be less specialized to some in between thing for this specific
case to work but it's only because I used the term cat food
rather than some other more general class.

Animal -> Animal food
Koala   -> Koala food

A Koala only eats specific types of food, nothing else. We can
call that Koala food.

As an animal, koala food is still animal food, so casting still
works. It is only the upcasting that can fail. But that is true
in general(we can't cast all animals to Koala's... and similarly
we can't cast all animal food to all Koala food). D's cast will
only enforce one side because he does not have the logic deal
with dependent parallel types.

This is a very natural thing to do. Haskell can handle these
situations just fine. With D, and it's inability to specify the
relationship dependencies, it does not understand that things are
more complex.

Hence we can, in D, put any type of food in Koala in violation of
the natural transformations we want:

(cast(Animal)koala).food = catFood;

This is a violation of the structure but allowable in D due to it
not being informed we cannot do this. If we had some way to
specify the structure then it would result in a runtime
error(possibly compile time if it new koala was a Koala and could
see that we are trying to assign catFood to a KoalaFood type).

auto a = (cast(Animal)koala);
a.food = someFood;
auto k = cast(Koala)a;
k.food =?= someFood; // error

of course, if cast worked using deeper structural logic then
k.food would be null or possibly k would be null(completely
invalid cast).

You have to realize that I am talking about applying constraints
on the type deduction system that do not already exist but that
actually make sense.

If you wanted to model the animal kingdom and made a list of all
the animals and all the food they ate, there would be
relationships. Some animals will eat just about anything while
others will eat only one thing.

Animals                Foods
...                    ...

If you were to model this in using classes you would want some
way to keep some consistency.

If you do

class Animal
{
Food food;
}

class Koala : Animal
{

}

Then Koala allows *any* food... then you have to be careful of
sticking in only koala food! But if we *could* inform the
compiler that we have an additional constraint:

class SmartKoala : Animal
{
KoalaFood : Food food;
}

then SmartKoala will be able to prevent more compile time errors
and enforce the natural inherence relationship that exists
between animals on food.

We can do this with properties on some level

class Animal
{
property void food(Food food);
}

class SemiSmartKoala : Animal
{
override  property void food(Food food) { if
(!is(typeof(food) == KoalaFood)) throw ... }
}

This, of course, only saves us at runtime and is much more
verbose and is not really naturally constraining dependencies.
```
May 27 2018
JN <666total wp.pl> writes:
```On Sunday, 27 May 2018 at 06:00:30 UTC, IntegratedDimensions
wrote:
animal a = new cat();

a.f = new food()
auto c = cast(cat)a;

as now f in cat will be food rather than catfood.

I think the problem is in your hierarchy. If Animal can have
Food, that means that any animal should be able to accept any
food, without knowing what kind of food is this. Cat requiring
cat food is a leaky abstraction, the cat shouldn't know nor care
what kind of food it gets, as it's an animal and it will eat any
food.
```
May 27 2018
IntegratedDimensions <IntegratedDimensions gmail.com> writes:
```On Sunday, 27 May 2018 at 18:16:25 UTC, JN wrote:
On Sunday, 27 May 2018 at 06:00:30 UTC, IntegratedDimensions
wrote:
animal a = new cat();

a.f = new food()
auto c = cast(cat)a;

as now f in cat will be food rather than catfood.

I think the problem is in your hierarchy. If Animal can have
Food, that means that any animal should be able to accept any
food, without knowing what kind of food is this. Cat requiring
cat food is a leaky abstraction, the cat shouldn't know nor
care what kind of food it gets, as it's an animal and it will
eat any food.

This is clearly false.

A Kola does not eat any type of food, nor does a whale or most
animals.

While it is just an example, it still applies in general. Natural
transformations are fundamental to type theory.

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. Now cat has
dog food, which is invalid.

hierarchy is maintained to be non-leaky at runtime(I never down
cast). 2. casting can be designed to handle this behavior. When
we cast animal to cat then the cast can null dog food since it
isn't cat food. No different than if we cast animal to hammer but
requires a bit more compiler logic.
```
May 27 2018
```On Sunday, 27 May 2018 at 20:50:14 UTC, IntegratedDimensions
wrote:
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. Now
cat has dog food, which is invalid.

It sounds like you don't want to have a `food` setter in the
`Animal` base class. Instead, you want setters in each child
class that take the specific type required, and possibly an
abstract getter in the base class. You could use a mixin to ease
the process of defining appropriate food types, and you could
have a method that takes the base `Food` class and does runtime
validation.

You might also change `Animal` to `Animal!(TFood : Food)` and go
from there. You'd likely need to extract another base class or
interface so you can have a collection of arbitrary animals.
```
May 27 2018
IntegratedDimensions <IntegratedDimensions gmail.com> writes:
```On Sunday, 27 May 2018 at 21:16:46 UTC, Neia Neutuladh wrote:
On Sunday, 27 May 2018 at 20:50:14 UTC, IntegratedDimensions
wrote:
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.
Now cat has dog food, which is invalid.

It sounds like you don't want to have a `food` setter in the
`Animal` base class. Instead, you want setters in each child
class that take the specific type required, and possibly an
abstract getter in the base class. You could use a mixin to
ease the process of defining appropriate food types, and you
could have a method that takes the base `Food` class and does
runtime validation.

You might also change `Animal` to `Animal!(TFood : Food)` and
go from there. You'd likely need to extract another base class
or interface so you can have a collection of arbitrary animals.

The problem with all this is that it is not the correct way. For
N types you have to scale: Animal(Food, Skin, Eyes, ...).

While not having a specific setter in the Animal class does solve
the problem of preventing assignment and sorta solve the problem
by requiring assignment to occur at the proper object it does not
solve the general problem. If we create an animal we should we
able to assign it animal food(rather than tools). In a sense this
goes a bit too far. Remember, a type hierarchy could be much more
complex and we can have types of types which will then all have
to follow these "workarounds" resulting if a very complex mess.

life
domain
kingdom
phylum
class
order
family
genus
species
...
Within each of these types there are specializations

the human species is a type of species in the homo genus, etc.

Now, how can we model such a thing be providing maximum compile
time typing structure?

class life; // Sorta like object, something that can be anything
and contains only the common structure to all things that can be
considered living

class domain : life;
...

class species : genus;
class human : species;

Now, this is all standard. But all types will *use* other types.
Humans will use, for example, tools. So we will have another
complex hierarchy where tools fall somewhere

class tool : object;
class hammer : tool;

so

class human : species
{
tool[] t;
}

Now, suppose we have bob the house builder, a human,

class houseBuilder : humanWorker;

in which we could add a tool to bob

auto bob = new houseBuilder();
bob.tools ~= new hammer();

All fine an dandy!

This is because tool there is not a natural transformation
between human and houseBuilder and tool and hammer. There a
hammer being derived from a tool in no way corresponds to a
houseBuilder being derived from a human. The "uses" in this case
is from types to types and objects to objects (humans use tools
and bob uses a hammer).

For the structure I am talking about, "parallel inheritance"
there is a natural relationship between types to types and types
to types that naturally transform in "parallel".

To understand this we need to think about something that
parallels our taxonomy so that as we inherit through one there is
a *natural* inheritance through the other and a sort of
"correspondence"(the natural transformation) that keeps
everything aligned.  Sorta like a ladder where we can only move
up and down but each end of a rung.

Life         ->    A
Domain       ->    B
...          ->    ...
Genus        ->    X
Species      ->    Y
Human        ->    Z
houseBuilder ->    _

Now, if your paying attention, Human is actually not part of the
taxonomy above, it is a specialization of Species.

Unfortunately in D we only have one level of typing rather that
types of types. We only have a type. The taxonomy above would be
a type of a type of an object while humans would be a type of
object. So what happens is the different conceptualizations are
conflated. Since types can be treated as sets, this is like
saying that sets of sets are the same as sets. Well, sets of sets
are sets but not all sets are sets of sets, hence they are not
exactly the same(it is inclusion rather than equality).

{1,2,3} is not a set of sets. (although, it is true we can treat
1,2,3 as sets and so we could say it is but but I don't want to
get in to the sets of things that are not sets problem).

So, really what we have is that the taxonomy is precisely this
paralleling that goes on:

Life         ->    Life
Domain       ->    Eukaryote
...          ->    ...                    ...           ...
Genus        ->    Homo          _>       ...           ...
Species      ->    Human         _>   houseBuilder  --> bob   -->
hammer

So, for example, Eukaryote's have cells that build things and
different ways to class them. Hence "Builders" would have have a
hierarchy of things that build stuff(A heart cell, like bob,
builds a heart using a "hammer").

The point with all this is that somethings have natural
transformations(by design) and some things don't but
unfortunately in D we must represent it all with classes since we
have no way to specify any higher order type. A human is
contained within the type Species but it it is not a sub type of
species. There is a huge difference. This is why we say "The
*Human* Species" and not the "Genus Species"(If human where a sub
type of species then species would be a sub type of genus and we
could say it and it would make sense). Similarly, bob is of type
human but house Builder is not but bob is a type of houseBuilder.

There are all kinds of *types* of relationships and they are
generally confused and conflated. Depending on the problem one
can get away with it or finagle code to make things work.

The only way D can handle this problem is by keeping class
hierarchies distinct which is what most people are able to do
naturally anyways. Even though classes are classes, if they are
unrelated it is sort of like grouping them in to different type
sets.

The problem comes from when they are not completely unrelated. In
D we only have the ability to include as an "element" an object(a
field), its type(the field type), and template parameters. This
type of dependency, inclusion, is very open. It really allows
just about anything to be included as long as the type matches.
We can even make it more free or less by using template
parameters:

class A(T)
{
T a;
}

here A depends on any type T, which makes A more general than
A!int. Of course, we must always specify T at some point so
ultimately A is just as restrictive as  A!int, so we only gain
syntactic sugar using template parameters(everything that can be
done with template parameters can be done without).

We can do have some tools to restrict T though using constraints:

class A(T)
if (T is int)
{
T a;
}

and this helps quite a bit since we could do

class A(T)
if (is(T : Q))
{
T a;
}

but even with all this we can't do something very simple:

class A
{
T a;
}

class C : A
{
TT : T a;
}

We can't specify parallel restrictions.

If C inherent from A then TT must inherent from T.

Sure we can use template parameters for a small number of
inclusions but the code bloat grows potentially exponentially and
does this really solve the problem?

class bit;

class ebit : bit
{
bit[8] data;
}

class number
{
bit[] a;
}

class byte : number
{
(ebit : bit)[] a;
}

every byte is a number and every ebit is a bit(using the
composite pattern). They are separate "classes" of type
relationships;

number      bit

byte        ebit

We can always go up the ladder. A double is a number and an ebit
is a bit.

We cannot always go down the ladder. Not all numbers are byte and
not all bits are ebits.

We, though mix and match in one way. A number can use an
ebit(since it is just a bit). The only case that fails is when a
byte, treated as a number, uses only a bit. This failure case is
no different than trying to treat a bit as an ebit or a number as
a double when they are not.

What the above does is essentially expand every bit to be 8 bits.
This would scale all number by a factor of 8 bits and 7 of those
would not be used when treated as a number.

Now, if we can't specify such a nice parallel relation we are
stuck using bit rather than ebit when ebit is a more natural type
to use as it relates to byte.

What you should realize is that the failing condition is the same
condition that fails in general, trying to upcast an object in
the wrong type. This is always the problem and is no different
here. We just have two parallel type hierarchies that are
naturally related and so the up casting is more complex as it
must deal with both sides(or many sides).

For multiple inclusions the process is just as simple. If we are
up casting n type hierarchies then each type must be able to up
cast. Hence for N inclusions there are N+1 potential ways to fail:

class A
{
T1 a;
T2 b;
T3 c;
}

class C : A
{
TT1 : T1 a;
TT2 : T2 b;
TT3 : T3 c;

}

and when casting from an A to a C, it may be that any combination
of a,b,c contain invalid types which can't be cast. Although,
with proper design everything should fail together or not fail at
all.

All this is really no different than the single object case. You
can always down cast but can only upcast if consistency is
preserved. Here we just have to check several cases and deal with
the slight complexity that having multiple cases gives. In the
singular case we either get the object or null. In the multiple
case we get an N tuple case that is either the object or null.
For C above we get the possibilities (C | null, TT1 | null, TT2 |
null, TT3 | null).
```
May 27 2018
"Nick Sabalausky (Abscissa)" <SeeWebsiteToContactMe semitwist.com> writes:
```On 05/27/2018 04:50 PM, IntegratedDimensions wrote:
On Sunday, 27 May 2018 at 18:16:25 UTC, JN wrote:
On Sunday, 27 May 2018 at 06:00:30 UTC, IntegratedDimensions wrote:
animal a = new cat();

a.f = new food()
auto c = cast(cat)a;

as now f in cat will be food rather than catfood.

I think the problem is in your hierarchy. If Animal can have Food,
that means that any animal should be able to accept any food, without
knowing what kind of food is this. Cat requiring cat food is a leaky
abstraction, the cat shouldn't know nor care what kind of food it
gets, as it's an animal and it will eat any food.

This is clearly false.

A Kola does not eat any type of food, nor does a whale or most animals.

Exactly. That's why your hierarchy is wrong. If any Animal COULD eat any
food, THEN you would want the Animal class to contain a Food object.
Clearly that's not the case. Clearly, any Animal CANNOT eat any food,
therefore the Animal class should not contain a Food object. At least, I
think that's what JN was saying.

Of course, removing Food from the Animal class *does* cause other
problems: You can no longer use OOP polymorphism to tell any arbitrary
Animal to eat.

(Part of the problem here is that using base classes inherently involves
type erasure, which then makes a mess of things.)

Honestly though, the real problem here is using class hierarchies for
this at all.

Yea, I know, OOP was long held up as a holy grail. The one right way to
do everything. But it turned out class hierarchies have a lot of
problems. And you're hitting exactly one such problem: There's many
modelling scenarios they just don't fit particularly well. (And this
isn't a D problem, this is just OOP class hierarchies in general.)

D has plenty of other good tools, so it's become more and more common to
just avoid all that class inheritance. Instead of getting polymorphism
from class inheritance, get it from templates and/or delegates. Things
tend to work better that way: it's more flexible AND gives better type
safety because it doesn't necessitate type erasure.

This does involve approaching things a little bit differently: Instead
of thinking/modelling in terms of nouns, think more in terms of verbs.
It's all about what you're *doing*, not what you're modelling. And
prefer designing things using composition ("has a") over inheritance
("is a").

So, regarding the Animal/Food/Cat/CatFood example, here is how I would
approach it:

What's something our program needs to do? For one, it needs to feed an
animal:

void feedAnimal(...) {...}

But in order to do that, it needs an animal and a food:

void feedAnimal(Animal animal, Food food) {
animal.eat(food);
}

That's still incomplete. What exactly are these Animal and Food types?

There are different kinds of animal and different kinds of food. We have
two main options for handling different kinds of Animal and Food, each
with their pros and cons: There can be a single Animal/Food type that
knows what kind of animal, or each kind of animal/food can be a separate
type.

A. Single types (many other ways to do this, too):

struct Animal {
enum Kind { Cat, Dog, Bird }
Kind kind;

// Eating is overridable:
void delegate(Food) customEat;

void eat(Food food) {
enforce(food.kind == this.kind, "Wrong kind of food!");

if(customEat !is null)
customEat(food);
else
writeln("Munch, munch");
}

Food preferredFood() {
return Food(kind);
}
}

struct Food {
Animal.Kind kind;
}

Animal cat() {
return Animal(Animal.Kind.Cat, (Food f){ writeln("Munch,
meow"); });
}
Animal dog() {
return Animal(Animal.Kind.Dog, (Food f){ writeln("Munch,
woof"); });
}
Animal bird() {...}

Animal[] zoo;

B. Separate types (many other ways to do this, too):

import std.traits : isInstanceOf;
import std.variant : Variant;

struct Cat {
void eat(Food!Cat) { writeln("Munch, meow"); }
}
struct Dog {
void eat(Food!Dog) { writeln("Munch, woof"); }
}
struct Bird {...}

enum isAnimal(T) =
/+ however you want to determine whether a type is Animal +/

struct Food(Animal) if(isAnimal!Animal) {}
enum isFood(T) = isInstanceOf!(Food, T);

void feedAnimal(Animal, Food)(Animal animal, Food food)
if(isAnimal!Animal && isFood!Food)
{
// Compiler gives error if it's the wrong food:
animal.eat(food);
}

Variant[] zoo;
```
May 27 2018
Basile B. <b2.temp gmx.com> writes:
```On Sunday, 27 May 2018 at 06:00:30 UTC, IntegratedDimensions
wrote:
(see my other most recent post for disclaimer)

My designs generally work like this:

Main Type   uses   Subservient types
A                      a
B                      b
C                      c

where C : B : A, c : b : a.

In the usage I must also keep consistent the use of c in C, b
in B, a in A. There is little if nothing in the type system
that allows me to specify and force this type of relationship.
Although there is some covariant functions that do help such as
overriding properties with covariance.

class a;
class b : a;

class A { a x;  property a X() { return x; }  property void X(a
v) { x = v; } }
class _B { b x;  property b X() { return x; }  property void
X(a v) { x = v; } }
class B : A {  property b X() { return cast(b)x; }  property
void X(b v) { x = v; } }

Note that class _B is basically that of A which changes the
type a in to a b but otherwise is identical. This is to prove a
point of relationship in that _B uses b just as B should.

Class B tries to treat x as a type b as much as possible. IN
fact, by design, x is always assigned a b.

In this case, the design is safe by effort rather than type
consistency.

A f = new B();

f.x is a type of b which is of type a, so no violations here.

B g = new B();

g.x is of b type of so no violations.

but note that

f.x and g.x both accept type a. Of course g.X enforces type
safety.

Effectively the subservient types always grow with the main
types in parallel so they never get out of step. In category
theory this is equivalent to a natural transformation.

A -> a
|    |
v    v
B -> b

Ideally one simply should express this in a meaningful way:

class B : A
{
extend b : a x;
property b X() { x; } // note that we do not need a cast
property void X(b v) { x = v; }
}

the syntax tells the compile that x of type a in A is of type b
in B and that b must extend a. This then gives us something
more proper to _B.

Note that now

B g = new B();

g.x = new a(); // is invalid g.x is forced to be b which is
derived from a(or anything derived from b)

These designs are very useful because they allow a type and all
it's dependencies to be extended together in a "parallel" and
keep type consistency. Natural transformations are extremely
important in specify structural integrity between related
types. While D allows this using "hacks"(well, in fact I have
yet to get the setter to properly work but it is not necessary
because of direct setting and avoiding the property setter).

This is a potential suggestion for including such a feature in
the D language to provide sightly more consistency.

Here is a "real world"(yeah, right!) example:

class food;
class catfood;

class animal { food f; }
class cat : animal { catfood : food f; }

animal  ->   food
|            |
v            v
cat     ->   catfood

Of course, I'm not sure how to avoid the problem in D of

animal a = new cat();

a.f = new food()
auto c = cast(cat)a;

as now f in cat will be food rather than catfood.

The cast may have to be applied for the subservient types too
internally. (which the compiler can do internally) but should
the main object be set to null or just the subservient object?

auto c = cast(cat)a; // if cast(cat) is extended should c be
null or just c.f? The first case is safer but harder to fix and
is the nuke option. In this case one might require two casting
methods

auto c1 = cast(cat)a; // c1 is null
auto c2 = ncast(cat)a; // c2 is not null, c2.f is null

Thoughts, ideas?

1/ I think that signatures could solve this problem in a nice way
(https://github.com/rikkimax/DIPs/blob/master/DIPs/DIP1xxx-RC.md).

2/ For now you can solve the problem with a "template this"
parameter. This is not a perfectly clean solution but not an ugly
one either. You don't need to rewrite the x setter and getter and
in order to cast because you get the most derived type in the
calling context (although the compiler still does a dynamic cast,
but not for the "parallel type", which is provided by a "mixin
template").

```
module runnable;

class a {}
class b : a {}
class c : b {}

mixin template Extensible(E)
if (is(E : a))
{
alias Extended = E;
Extended _x;
this()
{
_x = new Extended;
}
}

class A
{
mixin Extensible!a;
property auto x(this T)() { return (cast(T) this)._x; }
property void x(this T, V)(V v) { (cast(T) this)._x = v; }
}

class B : A
{
mixin Extensible!b; // extend b : a x;
}

class C : B
{
mixin Extensible!c; // extend c : b x;
}

void main()
{
B someB = new B;
C someC = new C;
static assert(is(typeof(someB.x()) == b));
static assert(is(typeof(someC.x()) == c));
}
```

What's not nice is that even if the "template this" parameter
allows to select the right x, there's still an instance of x for
each sub class.
```
May 27 2018
```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. […]
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.
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
```
May 27 2018