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.












Reply via email to