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