D modeling
CheeseWiz
CheesyFritos at gmail.com
Tue Jul 2 01:21:56 UTC 2019
Here is the code, It would be nice if D could do proper modeling
of models. One can understand the issues as described in the
comments in the main. The idea is simple, we have a model(a set
of interrelated classes that are used for some purpose(which can
be called a model or a framework)) and we want to derive an new
model from it(using inheritance of models, it's similar to
inheritance of classes but is based on the set and internal
structure). Any model derived from another contains the base
model and it's structure exactly how any class that derives from
any base class will contain the base class structure.
But since D does not know about models it makes makes it
difficult to do such things and makes for ugly code. If the
language and compiler could understand models we could extend
models perfectly. We would only have to add whatever new behavior
we want or change the behavior we want.
There are 3 main problems:
1. Because we cannot use multiple inheritance, this makes the
code extremely verbose. We must use interfaces and the syntax
becomes unnecessarily verbose, but it does work ultimately. Model
inheritance has no issues with the diamond problem. Only one
branch will ever be used(either through the base model or, when
overridden, through the derived model).
2. We must override and alias members that ultimately do not need
to be expressed. The compiler can figure it out if it understood
what was being done.
3. Oop contravariance breaks the model logic and mixes
dependencies. It is a non-issue in modeling. In D it requires us
to cast everywhere. This is the main issue. You can see the issue
at work using IsItTasty functions. It's simple, Contravariance
prevents us staying with the derived model, we have to use the
base model(for return types, same applies to arguments with
covariance). But when we are in a derived model we are in it, we
are using everything in the derived model that belongs to it(and
behind them is the base model, but we do not directly refer to
the base model), at least that would be the plan.
//f.IsItTasty; // Error: no property
The issue is with this line, it should just work, but D complains
because it is trying to use the base model, yet, for all
practical purposes the base model does not exist. The base model,
in modeling, is completely occluded by the derived model(although
maybe we would need a `supermodel.` type of accessor like we have
with `super.`, although `super.` may work just fine)
Here is the code:
import std.stdio, std.traits;
struct ModelA
{
// D only allows single inheritance, must use interfaces
interface iAnimal
{
string Type();
string Name();
void Attack(iAnimal who);
iFood LikesWhichFood();
}
interface iCat : iAnimal
{
void Meow();
}
interface iDog : iAnimal
{
void Bark();
}
interface iFood
{
}
class Animal : iAnimal
{
void Attack(iAnimal who) { writeln(Name, " is attacking
", who.Name, "!"); }
string Type() { return "Unknown Animal Type"; }
override string Name() { return "Unknown Animal"; }
iFood LikesWhichFood() { writeln("Food D Type: ",
fullyQualifiedName!iFood); return null; }
}
class Cat : Animal, iCat
{
string name = "Unknown Cat";
override string Type() { return "Cat"; }
override string Name() { return name; }
void Meow() { writeln("Meow!"); }
this() { }
this(string n) { name = n; }
}
class Dog : Animal, iDog
{
string name = "Unknown Dog";
override string Type() { return "Dog"; }
override string Name() { return name; }
void Bark() { writeln("Bark!"); }
this() { }
this(string n) { name = n; }
}
class Food : iFood
{
}
}
// Model B, It is "derived" from A, meaning Model B could, in
theory, substitute for Model A as long as everything is designed
correctly
// In this case we will create a ViewModel, a gui framework for
ModelA. We actually cannot do this naturally in D since it does
not support multiple inheritance.
struct ModelB
{
interface iAnimal : ModelA.iAnimal
{
}
interface iCat : iAnimal, ModelA.iAnimal
{
}
interface iDog : iAnimal, ModelA.iAnimal
{
}
interface iFood : ModelA.iFood
{
void IsItTasty();
}
class Animal : ModelA.Animal, iAnimal
{
}
class Cat : ModelA.Cat, iAnimal, iCat // We need to derive
from Animal, not iAnimal, to provide proper ModelB implementation
of Animal
{
alias Attack = Animal.Attack; // Required by D
// In D, ModelA.Cat's implement is not provided as default, we
have to reimplement everything. Or is Animal providing any
implementation
override string Type() { return super.Type; }
override string Name() { return super.Name; }
override void Meow() { super.Meow; }
void Attack(iAnimal who) { super.Attack(who); }
override void Attack(ModelA.iAnimal who) { super.Attack(who); }
override iFood LikesWhichFood() { writeln("Food D Type: ",
fullyQualifiedName!iFood); return new Cabbage; }
this() { }
this(string n) { name = n; }
}
class Dog : ModelA.Dog, iAnimal, iDog
{
alias Attack = Animal.Attack;
override string Type() { return super.Type; }
override string Name() { return super.Name; }
override void Bark() { super.Bark; }
void Attack(iAnimal who) { super.Attack(who); }
override void Attack(ModelA.iAnimal who) { super.Attack(who); }
override iFood LikesWhichFood() { writeln("Food D Type: ",
fullyQualifiedName!iFood); return new Donuts; }
this() { }
this(string n) { name = n; }
}
class Food : iFood
{
void IsItTasty() { writeln("Unknown Food"); }
}
class Donuts : Food
{
override void IsItTasty() { writeln("YUK!"); }
}
class Cabbage : Food
{
override void IsItTasty() { writeln("YUM!"); }
}
}
void main()
{
{
ModelA.iAnimal animal1 = new ModelA.Cat("Mittens");
ModelA.iAnimal animal2 = new ModelA.Dog("Sparky");
writeln(animal1.Name);
writeln(animal2.Name);
animal1.Attack(animal2);
animal1.LikesWhichFood;
}
writeln("\n----------\n");
{
ModelB.iAnimal animal1 = new ModelB.Cat("Super Mittens");
ModelB.iAnimal animal2 = new ModelB.Dog("Super Sparky");
writeln(animal1.Name);
writeln(animal2.Name);
animal1.Attack(animal2);
auto f = animal1.LikesWhichFood;
//f.IsItTasty; // Error: no property `IsItTasty` for type
`Models.ModelA.iFood`. It should return a ModelB.iFood, we are
inside ModelB, never any risk
(cast(ModelB.iFood)f).IsItTasty; // We can, of course, force
it, but that is the rub, we don't have to, that is why we want to
have a concept of a model, it tells the compiler that there is
something more going on and it can reduce all this overhead. We
can't even override this because of the contravariance rule.
}
writeln("\n----------\n");
// This is the magic, ModelB is now substituted in Model A. It's
basically still oop but our entire derived model is(or should be)
used.
// We can substitute the new model in all places where the old
was used. This is the easy way to do ModelViewModel, we simply
extend the model and add the view, no complex bridging, adapting,
maintance, dependencies, etc.
{
ModelA.iAnimal animal1 = new ModelB.Cat("Super Mittens");
ModelA.iAnimal animal2 = new ModelB.Dog("Super Sparky");
writeln(animal1.Name);
writeln(animal2.Name);
animal1.Attack(animal2);
animal1.LikesWhichFood;
auto f = animal2.LikesWhichFood;
//f.IsItTasty; // This Error is ok, we are inside ModelA,
ModelA would never use IsItTasty and it would be wrong to do
so(it's only wrong because it should be impossible for ModelA to
know about ModelB, else we create a dependency between models and
really end up with one combined model rather than two separate
models). But note that we could cast
(cast(ModelB.iFood)f).IsItTasty; // We can, of course, force
it though(only because we know for a fact we are actually dealing
with a ModelB disugised as a ModelA, this is generally not the
case), but this then shows a dependency. Note that it is exactly
like the above model though... but there is a huge difference. In
the first case it is afe, in this case it is not.. and the only
difference is the model we are working in.
}
}
It should be noted that modeling is a higher abstraction but is
effectively almost identical to standard oop. It's akin to
elements and arrays of elements. It's virtually not that much
more, except we do have to then add a few concepts such as
indexing, insertion, etc.
In D, the code is very verbose, I guess some meta programming
could reduce the verbosity quite a bit. Maybe it could even
reduce it all(using mixin templates to provide the default
implementation). I'm not sure if problem 3 could be fixed though,
if it could then there would be no issues with modeling in D
except verbosity, which, if meta program solved it then maybe
there would be no issues in D.
If D had introduced a new concept called a model, which is sort
of a collection of classes, then would could do something very
simple to create a new model:
model ModelA
{
class A { }
class B : A { A foo; }
}
model ModelB : ModelA
{
class A { B bar; } // bar is new functionality on top of
ModelA.A. foo returns ModelB.A, not ModelA.A(fixes problem 3)
// class B : A; // automatically uses ModelB.A and so has a
member named bar, we don't have to change it if we don't want.
}
The model keyword looks at the specified hierarchy of ModelA(in
this case just A, B and the inheritance morphism B : A) and we
can then derive from the entire model... very similar to how we
derive from classes.
auto m = new ModelA();
and
ModelA ma = new MoodelB();
I think D actually basically can do all this. If we replace model
with class and inner classes could be derived recursively a few
little extensions and we could just essentially use nested
classes:
class ModelA
{
class A { }
class B : A { }
}
class ModelB : ModelA
{
// The compiler extends inheritance to all classes of ModelA
recursive
// i.e. it adds class A : ModelA.A { } class B : A, ModelA.B
{ } // Multiple inheritance allowed
// Possibly allow direct modification such as override void
B.bar(). rather than having to do it inside the class of B. Would
make things a little less verbose.
}
Of course, to avoid it doing this on preexisting code we would
introduce the model keyword... which would probably break a lot
of code, so a better keyword would need to be used.
What is the use?
Imagine you write a calculator. You only write the business end,
no gui, no interface, etc.
Now you want to create a gui for it. you create a new model that
extends th calculator. You don't mix and match, don't create
ModelViewModel(well, you are but using models you don't have to
duplicate any code that is not needed.
model CalculatorGUI : Calculator
{
// Add only graphical necessities. The gui can be used
anywhere the calculator can, always providing a gui for any usage
}
E.g., suppose a bank around uses the Calculator to do math, you
could substitute the CalculatorGUI for it and then get a gui
version. Since CalculatorGUI is a Calculator, it will work just
fine, again, this is just oop but applied in a more sophisticated
way.
I have never seen a programming language use models like this.
They all leave it up to the programmer to deal with keeping
everything on the straight and narrow. Modeling is used
everywhere and constantly. In fact, class inheritance is just
very simple modeling.
I believe Haskell can do such things naturally but I don't know.
D can almost D things naturally but it could be made very natural
with a few modifications.
More information about the Digitalmars-d
mailing list