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

IntegratedDimensions IntegratedDimensions at gmail.com
Sun May 27 21:17:03 UTC 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.














More information about the Digitalmars-d mailing list