There's a proposal at the moment to add support for TDNR to Haskell
- to leverage "the power of the dot" (e.g. for intellisense).
http://hackage.haskell.org/trac/haskell-prime/wiki/TypeDirectedNameResolution
I approve of the goal, but I'd like to suggest a different approach.
My basic idea is stolen from Bertrand Meyer (Object-Oriented
Software Construction, second edition). Basically, a class *is* both
a module and a type. Quote...
Classes as modules
Object orientation is primarily an architectural technique: its
major effect is on the
modular structure of software systems.
The key role here is again played by classes. A class describes
not just a type of
objects but also a modular unit. In a pure object-oriented
approach:
Classes should be the only modules.
By the logic of equivalence relations, we can conclude that a type
*is* a module. Only I'd adapt that a little. In C++, the following
operators can all be used to access the "module" for some type or
value...
- :: Scope resolution
- . Member dereference
- -> Member dereference via a pointer
- .* Member-pointer dereference
- ->* Member-pointer dereference via a pointer
In C++, a type and an instance each have their own modules. A
(smart) pointer has its own module, separate from the module for the
type it points to. And member-pointers exist because sometimes
there's a need to reference a member without knowing or (yet) caring
which instance.
We already have member pointers - the functions that map an instance
to the field value. It would make some sense if these could be
placed in a module associated with the type (not the instance).
When an instance is created of a type, that can effectively (without
run-time overhead) create a new module associated with the new
instance. This will contain the same field-access functions, but
with the instance parameter already curried in.
So there's no real need for any new meaning of the . operator - it's
just access to names within a module. And there's no need for a new
mechanism for accessing fields - only for a way to place them in
that module scope, and a little sugar that gives us the same
field-access function but with the instance parameter already
curried in.
Once we have these modules containing compiler-generated
field-access functions, though, it makes some sense to allow
additional functions (and perhaps types) to be added within that
types module explicitly by the programmer. It may also make sense to
allow functions to be explicitly defined which will be added to the
instance-modules and support the prefix-instance-parameter sugar.
Finally, as with C++, when dealing with IORef and similar, it make
make sense to have a separate -> operator (spelled differently,
of course). Or it could use the standard dot. C++ and D disagree in
this (in C++, the smart pointer has its own module separate from the
pointed-at instance - in D, there is no -> or equivalent).
As an aside, Ada has already gone through a related transition. The
original Ada 83 had variant records, but no "true classes". In Ada
95, "tagged types" were added which were like variant records, but
which supported inheritance and run-time dispatch. The discriminant
is replaced by a "tag" which is presumably implemented as a virtual
table pointer. However, functions and procedures weren't members.
The typical call of a "method" would be...
packagename.procedure_name ( instance_arg, other_args );
Ada 2005 added some workarounds to allow conventional OOP call
notation. See section 1.3 of the Ada 2005 rationale for details.
However, it all feels a bit kludgy. In particular, the procedures
and functions still aren't members - there are just some special
rules for when they can be used as if they were. I've not actually
used Ada 2005, but I'd bet some confusion can result from that.
Personally, I think Meyer was at least partly right - if types (and
instances) are modules, the kludge-factor is much lower. C++
actually doesn't get this quite right IMO (you can access static
class members through the instance objects, for example, not just
through the classes), but C++ classes *do* act mostly like modules
and that is a very useful trait - particularly within the
declarative sublanguage (templates etc).