It kinda dawned on me that there are two major issues with
programming:
[Internal code = code created by you/me, User code = code created
by the user to use our code]
1. Extensibility -
When we write code that will be used(by anyone), essentially it
is wrapped in another layer of indirection. Each time, each
"flaw" in the reduces the code flexibility and hence
extensibility suffers.
2. Dependency Levels -
When we create explicit dependencies of dependencies then the
flaws become magnified. Since the number of "cross-references" is
generally exponential. (e.g., old school programming basically
ignores this factor, which is why it is so hard to maintain).
---------
Example: [Assume no null objects and everything public]
Internal:
class Mine
{
void foo();
}
User:
class Yours
{
Mine m;
}
----
Yours y;
y.m.foo(); <-- a 2-level dependency
This is hard to maintain!!!!
Since no changes can be made to the interface of Mine.(we could
obviously make it much worse by having parameters for foo)
class Yours
{
Mine m;
void foo() { m.foo; }
}
y.foo(); <-- much better, since if m changes, it won't break
anything that depends on Yours since foo will exist. Simply
updating Yours.foo is enough to work with m is enough. This is
basic oop of course.
---
But if one logically extends this, would we not want remove
multiple indirections? This creates a sort of chain of
dependencies rather than a complex structure of them. (sort of
like chain mail vs a chain)
Again, this is basic oop.
But going further:
What would we not want to make all indrections only one level
deep?
e.g.,
No, Hers.His.Theirs.Ours.Yours.Mine.foo();
Each level creates a serious dependency issue if Ours.foo is
changed. We have to deal with 6 levels of dependencies scattered
across an untold number of real dependencies. It is an
exponential increase in complexity!
(every line of that uses some form of indirection to get at foo
creates a new flaw)
But there seems to be a solution!!
Never allow one to have more than one level of indirection.
E.g., Hers.foo(); (foo is not mine foo but a wrapped version).
Having more than one level reduces extensibility, makes code
harder to maintain, etc.
But essentially this requires wrapping all the methods at each
level. This is linear rather than exponentially but still a lot
of typing. (but ultimately not more than the typing that one has
to do to fix the flaws that each level of indirection creates)
What I'm thinking is that it would be better to expose wrappings
for every member of every field and all the members of every
field object. (this, of course, is exponential too)
This allows us to always use only one level of indrection(e.g.,
Yours.foo rather than Yours.Mine.foo).
D can already do this using OpDispatch!!! (this allows us to do
it programmatically)
IDE's could do this also by simply providing default wrappers to
for all cases. (sort of like implement interface in Visual
Studio, but done automatically... or reflection could be used,
etc)
The problem, of course, is name collisions and name
pollution(sort of related).
Is there a solution?
Possibly a new symbol is needed to prevent both name collisions
and allow for such capabilities? e.g., Yours.Mine`foo. In this
case, Mine`foo is not Mine.foo in the standard sense, but simply
calling a member of Yours called Mine`foo.
But there is a better way?
If compilers only allows one level of indirection, then
Yours.Mine.foo makes sense. The first . is standard member
access. The Mine.foo part is not another level of indirection of
simply name disambiguation. (it's like Mine`foo)
In this case though, we'd have effectively two levels of
indirection and not really solved the problem.
But if we can "alias" Mine inside Yours then we have made some
headway.
e.g.,
class Yours
{
alias Mine = NotMine;
void Mine.foo(); // actually calls NotMine.foo
}
Of course D can already do this too!!
Ultimately we are trying to gain the "dependenciness" of a flat
hierarchy with the separation that nesting provides.
D seems to provide some great mechanisms to allow one to treat
things as a semi-flat hierarchy but still have the indirection
capabilities.
Is there more that can be done? It seems that most languages give
the programmer full freedom to die in quicksand(you can choose as
little and as much levels of indirection you want to use in any
place). D allows some ability for the programmer to create a
systematic way to deal with the problem but still requires the
programmer to deal with this issue(e.g., we would still have to
implement OpDispatch and use aliases, etc)
Is there a more consistent approach? A language that is naturally
designed around preventing dependency explosion yet not
compensating with requiring a large amount of boilerplate code(in
which the compiler would actually take care of)?
Basically most languages have virtually no consistent view on the
issue. D has the tools but they feel more like "add ons" after
the fact rather than a natural part of the language that is
designed to help the programmer with such issues.