General problem I'm having in D with the type system
Nick Sabalausky (Abscissa)
SeeWebsiteToContactMe at semitwist.com
Mon May 28 03:31:07 UTC 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;
More information about the Digitalmars-d
mailing list