Thread overview
General problem I'm having in D with the type system
May 27, 2018
Vijay Nayar
May 27, 2018
JN
May 27, 2018
Neia Neutuladh
May 27, 2018
Basile B.
May 27, 2018
David Nadlinger
May 27, 2018
(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 27, 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 27, 2018
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
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.

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). 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 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 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
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
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
May 27, 2018
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
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? We haven't made them yet.

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;