General problem I'm having in D with the type system

Basile B. b2.temp at gmx.com
Sun May 27 21:01:04 UTC 2018


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.


More information about the Digitalmars-d mailing list