Transition for removing functions from Object [Compiler devs in particular, please read]
Jonathan M Davis
jmdavisProg at gmx.com
Sun May 26 18:20:03 PDT 2013
A few months back, we agreed that we'd be better of if we removed toString,
toHash, opEquals, and opCmp from Object. They don't need to be there, and
they're causing us havoc with attributes like const, pure, nothrow, and @safe,
because every class object in all of D has to follow the attributes that
Object has for them, which prevents certain idioms (and which idioms it
prevents depends on what the attributes on Object's functions are). For
instance, the only reason that comparing const class objects even works right
now is because druntime is casting away const to do the comparison (which is
_not_ good). So, we're removing those functions. But that raises the question
of what we have to do in order to do that with minimal code breakage.
There are four enhancement requests for this (one for each function):
http://d.puremagic.com/issues/show_bug.cgi?id=9769
http://d.puremagic.com/issues/show_bug.cgi?id=9770
http://d.puremagic.com/issues/show_bug.cgi?id=9771
http://d.puremagic.com/issues/show_bug.cgi?id=9772
Also, I have a pull request open
https://github.com/D-Programming-Language/druntime/pull/459
which will make it so that you can declare an opEquals on classes which
compares your own type rather than Object (you'd presumably pick the least
derived type in your hierarchy that you want to be able to compare). But this
only partially solves the problem for opEquals and does nothing for the
others.
We need a way to deprecate these functions on Object without breaking code.
This is _almost_ possible. One solution would be to make all 4 of those
functions free functions in object_.d so that they can be used with Object via
UFCS. We then deprecate those functions. However, this doesn't quite work for
three reasons:
1. You can't do UFCS with overloaded operators, and opEquals and opCmp are
overloaded operators. In general, I think that it would be bad to be able to
overload operators via UFCS (especially with functions that are as core as
opEquals and opCmp), but if we could at least make it so that the compiler
allowed it in this case until the deprecation process has been completed, then
these functions could be UFCS-able.
2. super doesn't work with UFCS. Take this code for example
---------
import std.stdio;
class C { }
class D : C
{
void foo() { writeln("D"); }
void bar() { writeln(super.foo()); }
}
void foo(C c) { writeln("C"); }
void main()
{
(new C).foo();
(new D).foo();
(new D).bar();
}
---------
Assuming that super worked with UFCS, it should print
C
D
C
but it just won't compile. giving a rather bizarre error
q.d(16): Error: template std.stdio.writeln does not match any function
template declaration. Candidates are:
/usr/include/D/phobos/std/stdio.d(1694): std.stdio.writeln(T...)(T
args)
q.d(16): Error: template std.stdio.writeln(T...)(T args) cannot deduce
template function from argument types !()(void)
I'm not sure how much it makes sense for super to work with UFCS in general,
but if we make the 4 functions in question free functions, any code which was
calling them via super will break. I'm not sure that that would be a frequent
occurence (in fact, I have no idea why you'd ever want to do that given what
those functions do on Object), but it _is_ a potential point of breakage.
3. The override keyword makes it so that making those functions free functions
will break all code everywhere that uses them. override is fantastic for
catching changes in class hierarchies so that you can fix your code when base
classes change, but it does not give us a good deprecation path. So, if we
were to make these functions free functions, the compiler would have to be
adjusted so that in this particular case, it gave a message about it (and not
a warning, as that would cause code to break with -w) rather than giving an
error. I do think that this _should_ give an error normally, as you'd no
longer be overriding anything, but we need to be able to not make it an error
in this case if we want a smooth transition. override is intended for catching
bugs quite loudly and not for smooth deprecation paths.
With all 3 of these issues, compiler changes are required, and save perhaps
for #2 (super working with UFCS), I'd argue that they aren't changes that we'd
want to have normally accept as part of this deprecation path, so I don't know
how acceptable they are. Also, I have no idea how easy it would be to make
these changes in the compiler. But this approach is the only one that I've
been able to come up with for smoothly removing these functions from Object,
which we really need to do (and sooner rather than later in order to minimize
how much code has to be changed).
So, assuming that you managed to read all of this, what are your thoughts on
this? Is there anything obvious I'm missing? How feasible do you think this
is? And any ideas on how to improve upon what I'm suggesting?
- Jonathan M Davis
More information about the Digitalmars-d
mailing list