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.