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.