On Sunday, 27 May 2018 at 06:00:30 UTC, IntegratedDimensions wrote:
(see my other most recent post for disclaimer)

My designs generally work like this:

Main Type   uses   Subservient types
   A                      a
   B                      b
   C                      c


where C : B : A, c : b : a.

In the usage I must also keep consistent the use of c in C, b in B, a in A. There is little if nothing in the type system that allows me to specify and force this type of relationship. Although there is some covariant functions that do help such as overriding properties with covariance.

class a;
class b : a;


class A { a x; @property a X() { return x; } @property void X(a v) { x = v; } } class _B { b x; @property b X() { return x; } @property void X(a v) { x = v; } } class B : A { @property b X() { return cast(b)x; } @property void X(b v) { x = v; } }


Note that class _B is basically that of A which changes the type a in to a b but otherwise is identical. This is to prove a point of relationship in that _B uses b just as B should.

Class B tries to treat x as a type b as much as possible. IN fact, by design, x is always assigned a b.

In this case, the design is safe by effort rather than type consistency.

A f = new B();

f.x is a type of b which is of type a, so no violations here.

B g = new B();

g.x is of b type of so no violations.

but note that

f.x and g.x both accept type a. Of course g.X enforces type safety.


Effectively the subservient types always grow with the main types in parallel so they never get out of step. In category theory this is equivalent to a natural transformation.

A -> a
|    |
v    v
B -> b



Ideally one simply should express this in a meaningful way:

class B : A
{
  extend b : a x;
  @property b X() { x; } // note that we do not need a cast
  @property void X(b v) { x = v; }
}


the syntax tells the compile that x of type a in A is of type b in B and that b must extend a. This then gives us something more proper to _B.


Note that now

B g = new B();

g.x = new a(); // is invalid g.x is forced to be b which is derived from a(or anything derived from b)




These designs are very useful because they allow a type and all it's dependencies to be extended together in a "parallel" and keep type consistency. Natural transformations are extremely important in specify structural integrity between related types. While D allows this using "hacks"(well, in fact I have yet to get the setter to properly work but it is not necessary because of direct setting and avoiding the property setter).

This is a potential suggestion for including such a feature in the D language to provide sightly more consistency.


Here is a "real world"(yeah, right!) example:

class food;
class catfood;

class animal { food f; }
class cat : animal { catfood : food f; }


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;


as now f in cat will be food rather than catfood.

The cast may have to be applied for the subservient types too internally. (which the compiler can do internally) but should the main object be set to null or just the subservient object?

auto c = cast(cat)a; // if cast(cat) is extended should c be null or just c.f? The first case is safer but harder to fix and is the nuke option. In this case one might require two casting methods

auto c1 = cast(cat)a; // c1 is null
auto c2 = ncast(cat)a; // c2 is not null, c2.f is null




Thoughts, ideas?

1/ I think that signatures could solve this problem in a nice way (https://github.com/rikkimax/DIPs/blob/master/DIPs/DIP1xxx-RC.md).

2/ For now you can solve the problem with a "template this" parameter. This is not a perfectly clean solution but not an ugly one either. You don't need to rewrite the x setter and getter and in order to cast because you get the most derived type in the calling context (although the compiler still does a dynamic cast, but not for the "parallel type", which is provided by a "mixin template").

```
module runnable;

class a {}
class b : a {}
class c : b {}

mixin template Extensible(E)
if (is(E : a))
{
        alias Extended = E;
        Extended _x;
        this()
        {
                _x = new Extended;
        }
}

class A
{
        mixin Extensible!a;
        @property auto x(this T)() { return (cast(T) this)._x; }
        @property void x(this T, V)(V v) { (cast(T) this)._x = v; }
}

class B : A
{
        mixin Extensible!b; // extend b : a x;
}

class C : B
{
        mixin Extensible!c; // extend c : b x;
}

void main()
{
        B someB = new B;
        C someC = new C;
        static assert(is(typeof(someB.x()) == b));
        static assert(is(typeof(someC.x()) == c));
}
```

What's not nice is that even if the "template this" parameter allows to select the right x, there's still an instance of x for each sub class.

Reply via email to