(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?