Jump to page: 1 2
Thread overview
D modeling
Jul 02, 2019
CheeseWiz
Jul 02, 2019
FeepingCreature
Jul 02, 2019
CheeseWiz
Jul 02, 2019
Jesse Phillips
Jul 02, 2019
Jesse Phillips
Jul 02, 2019
CheeseWiz
Jul 03, 2019
Jesse Phillips
Jul 03, 2019
Bert
Jul 03, 2019
Jesse Phillips
Jul 03, 2019
Jesse Phillips
Jul 03, 2019
Bert
Jul 04, 2019
Jesse Phillips
Jul 05, 2019
CheeseWiz
Jul 05, 2019
Simen Kjærås
Jul 06, 2019
CheeseWiz
Jul 06, 2019
CheeseWiz
Jul 07, 2019
Simen Kjærås
Jul 07, 2019
Jesse Phillips
Jul 07, 2019
CheeseWiz
Jul 07, 2019
Jesse Phillips
July 02, 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.





July 02, 2019
On Tuesday, 2 July 2019 at 01:21:56 UTC, CheeseWiz wrote:
> 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.
>

Quite the opposite! Covariance actually saves you.

struct ModelB
{	
    interface iAnimal : ModelA.iAnimal
    {
        override iFood LikesWhichFood();
    }
July 02, 2019
On Tuesday, 2 July 2019 at 01:21:56 UTC, CheeseWiz wrote:
>
> 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).

I don't see where you used multiple inheritance. Turn everything into concrete/abstract classes and you should be fine.

>
> 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.

Are you referring to the "NotImplementedException" that proliferate most poorly designed abstractions?

>
> 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.


With D support for both covariance and contravariance, you should be covered. You desired model behavior should just fall into place.

Could you throw up a working example as a gist? Might take a hack at it.
July 02, 2019
On Tuesday, 2 July 2019 at 14:36:04 UTC, Jesse Phillips wrote:
>
>
> With D support for both covariance and contravariance, you should be covered. You desired model behavior should just fall into place.
>
> Could you throw up a working example as a gist? Might take a hack at it.

Here is a related article

https://he-the-great.livejournal.com/54436.html
July 02, 2019
On Tuesday, 2 July 2019 at 13:52:17 UTC, FeepingCreature wrote:
> On Tuesday, 2 July 2019 at 01:21:56 UTC, CheeseWiz wrote:
>> 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.
>>
>
> Quite the opposite! Covariance actually saves you.
>
> struct ModelB
> {	
>     interface iAnimal : ModelA.iAnimal
>     {
>         override iFood LikesWhichFood();
>     }

Ok, adding the follow does seem to work:

    interface iAnimal : ModelA.iAnimal
    {
		override iFood LikesWhichFood();
    }

then one has to do

    class Animal : ModelA.Animal, iAnimal
    {
// Note the cast, but it is ok if necessary since at least it would isolate it.
		 override iFood LikesWhichFood() { return cast(iFood)super.LikesWhichFood; }
    }


The goal here is to avoid having to add all the extra plumbing. Imagine a real project with 100's of classes and having to override and cast everything just to get it to function. It seems excessive. Maybe meta programming can fix it?

July 02, 2019
On Tuesday, 2 July 2019 at 14:36:04 UTC, Jesse Phillips wrote:
> On Tuesday, 2 July 2019 at 01:21:56 UTC, CheeseWiz wrote:
>>
>> 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).
>
> I don't see where you used multiple inheritance. Turn everything into concrete/abstract classes and you should be fine.

I didn't, D doesn't support MI.

One has to inherit the base model and the derived model relationships, that is MI.

    interface iDog : iAnimal, ModelA.iAnimal
    {

    }

    class Animal : ModelA.Animal, iAnimal
    {
		 override iFood LikesWhichFood() { return cast(iFood)super.LikesWhichFood;
    }

ideally we could just do

class Dog : Animal, ModelA.Animal

and that is MI. It would simplify both models

>
>>
>> 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.
>
> Are you referring to the "NotImplementedException" that proliferate most poorly designed abstractions?

		alias Attack = Animal.Attack;	// Required by D
		void Attack(iAnimal who) { super.Attack(who); }


D gave me an error and said I must use the alias(first line), without I get an error about a missing override.

Error: class `Models.ModelB.Cat` use of `Models.ModelA.Animal.Attack(iAnimal who)` is hidden by `Cat`; use `alias Attack = Animal.Attack;` to introduce base class overload set


>>
>> 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.
>
>
> With D support for both covariance and contravariance, you should be covered. You desired model behavior should just fall into place.
>

Yes, it seems that with the proper overrides it works. It is quite verbose, it would be amazing if all this stuff could be automated. It all is boilerplate code and requires for every model and every component of the model.

> Could you throw up a working example as a gist? Might take a hack at it.

The OP contains a working example. Adding the overrides does solve problem 3 and so it is possible to have model inheritance in D! That is good news(I suppose it should be obvious since it's just normal inheritance)!

Now the only goal is too make it simple to do. I imagine meta programming and mixin templates could solve this... Not sure though if one would need partial classes to make it work. (it would be ugly to also have to have a lot of mixin templates littered throughout)

The goal would be to create something as simple as

model A : B
{

   // Add whatever extensions one wants, all plumbing is taken care of.
}

July 03, 2019
On Tuesday, 2 July 2019 at 18:27:32 UTC, CheeseWiz wrote:
> 
> The OP contains a working example.

I don't want to pull all the pieces together.

Meta programing wouldn't likely help, there are too many rules about how your interface and inheritance needs to combine with the parent model that any meta programming would hide the intent.

I still don't believe interfaces are needed, nor your example of multiple inheritance.
July 03, 2019
On Wednesday, 3 July 2019 at 04:58:00 UTC, Jesse Phillips wrote:
> On Tuesday, 2 July 2019 at 18:27:32 UTC, CheeseWiz wrote:
>> 
>> The OP contains a working example.
>
> I don't want to pull all the pieces together.

There are no pieces to put together. It is just a direct copy and paste. I pasted directly the source code in to the editor... you have to do is find the first place(which starts with import) and scroll do the last line(which is the last } of main). You are making it far more difficult than it is.

The only difference is to get it truly working you have to add 2 lines of code to add the contravariance.

> Meta programing wouldn't likely help, there are too many rules about how your interface and inheritance needs to combine with the parent model that any meta programming would hide the intent.

Maybe, maybe not. There are not too many rules though. It's simply providing the right overrides and such

> I still don't believe interfaces are needed, nor your example of multiple inheritance.


Interfaces are required because D does not support MI of classes. You will not be able to substitute one model for the other without inheritance. It's required.

Any any object in the derived model HAS TO derive from the base model and the parent in the derived model. That is the whole point. That is multiple inheritance. It can't be done any other way, but D only lets one inherent multiply using interfaces.

You won't be able to get around that if this is going to be done using oop.

Now, maybe one can hack things and not use inheritance between models and then force some type of substitution scheme that works... but that is for someone else to figure out.

Everything actually works out in D, it's just verbose and cumbersome and for complex models it is error prone. Hence the only thing to do is find a better way to deal with it.
July 03, 2019
On Wednesday, 3 July 2019 at 13:34:38 UTC, Bert wrote:
> Interfaces are required because D does not support MI of classes. You will not be able to substitute one model for the other without inheritance. It's required.

All I did was remove the interfaces:

https://run.dlang.io/gist/3b14d69aa323d83e2736a0e2e0dece3f
July 03, 2019
On Wednesday, 3 July 2019 at 14:01:17 UTC, Jesse Phillips wrote:
> On Wednesday, 3 July 2019 at 13:34:38 UTC, Bert wrote:
>> Interfaces are required because D does not support MI of classes. You will not be able to substitute one model for the other without inheritance. It's required.
>
> All I did was remove the interfaces:
>
> https://run.dlang.io/gist/3b14d69aa323d83e2736a0e2e0dece3f

Now I remove your unneeded overrides:

https://run.dlang.io/gist/28168b9e1265ab05da310c527079f6ac
« First   ‹ Prev
1 2