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