A Fresh Look at Comparisons, Take 2

Yigal Chripun yigal100 at gmail.com
Fri Apr 18 08:01:47 PDT 2008


Janice Caron wrote:
> This is an improved proposal based on the suggestions of Steven,
> Yigal, Bruce and Paul. Basically, I've ditched my original proposal
> entirely, and replaced it with this one. It doesn't do exactly what
> the previous proposal suggested, but nonetheless, it will still do the
> "right" thing, at least in the eyes of the posters to the last thread.
>
>
> RULE 1:
>
> We have a new function paramter storage class, "explicit", which means
> that only objects of the exact compile-time type may be passed in.
> (Implicit casting is forbidden, apart from implicit casting to const).
> So, for example:
>
>     class A {}
>     class B : A {}
>
>     void f(explicit A a)
>     {
>         writefln("Got an A");
>     }
>
>     A a1 = new A;
>     f(a1); /* OK */
>
>     A a2 = new B;
>     f(a2); /* OK - we only care about the compile-time type */
>
>     B b = new B;
>     f(b); /* ERROR - WILL NOT COMPILE */
>
> However, we make an exception for implicit casts to const. Thus:
>
>     void f(explicit const(A) a)
>     {
>         writefln("Got a const A");
>     }
>
>     A a1 = new A;
>     f(a1); /* OK */
>
> The new keyword "explicit" can also be applied to the "this" parameter
> of member functions, as follows:
>
>     class A
>     {
>         explicit void f()
>         {
>             writefln("Got an A");
>         }
>     }
>
>     class B : A {}
>
>     A a1 = new A;
>     a1.f(); /* OK */
>
>     A a2 = new B;
>     a2.f(); /* OK - we only care about the compile-time type */
>
>     B b = new B;
>     b.f(); /* ERROR - WILL NOT COMPILE */
>
> Observe that explicit member functions cannot be inherited, however,
> the inheritance mechanism is able to "jump over" an explict function
> when attempting to find a match, in order to match the next
> non-explicit function. I didn't explain that very well, did I? Let me
> show you what I mean by example:
>
>     class A
>     {
>         int f() { writefln("In A"); }
>     }
>
>     class B : A
>     {
>         explicit int f() { writefln("In B"); }
>     }
>
>     class C : A
>     {
>     }
>
>     A a = new A;
>     B b = new B;
>     C c = new C;
>
>     a.f(); // prints "In A"
>     b.f(); // prints "In B"
>     c.f(); // prints "In A"
>
> Here, the call to c.f() matches no function in C. It cannot match
> B.f(), because B.f() is explicit. But it /can/ match A.f(). And so it
> does. As far as objects of static type C are concerned. B's explicit
> functions might as well not exist.
>
>
>
> RULE 2:
>
> Neither opEquals nor opCmp shall be defined in Object.
>
>
> RULE 3:
>
> When the compiler encounters an equality test, such as
>
>     if (a == b) ...
>     if (a != b) ...
>
> and no compile-time match can be found for a.opEquals(b), then the
> identity test shall be performed. That is, it shall be as if the
> programmer had typed:
>
>     if (a is b) ...
>     if (a !is b) ...
>
> Note that this rule is only applicable to classes. Structs which do
> not implement opEquals shall be compared for exact bitwise equality,
> as is the case currently.
>
>
> RULE 4:
>
> When the compiler encounters a comparison test other than equality, such as
>
>     if (a < b) ...
>
> and no compile-time match can be found for a.opCmp(b), then all such
> comparisons shall be compile-time errors.
>
> Observation: It should be possible for the programmer to test for this
> at compile time, by doing, for example:
>
>     static if (is(a < b))
>     {
>         ...
>     }
>
>
>
> RULE 5:
>
> No type A may be used as an associtive array key unless a match can be
> found for A.opCmp(A).
>
> Observation: It should be possible for the programmer to test for this
> at compile time, by doing, for example:
>
>     static if (is(int[A]))
>     {
>         ...
>     }
>
>
> RULE 6:
>
> Classes which implement opEquals and/or opCmp will be expected to
> implement them using the following signatures:
>
>     // For classes
>     class C
>     {
>         explicit bool opEquals(explicit const(C) c) const {...}
>         explicit int opCmp(explicit const(C) c) const {...}
>     }
>
>     // For structs
>     struct S
>     {
>         explicit bool opEquals(explicit ref const(S) s) const {...}
>         explicit int opCmp(explicit ref const(S) s) const {...}
>     }
>
> (However, I don't see any way of enforcing this, so this rule
> represents recommended good practice).
>
>
> RULE 7:
>
> The following tokens shall be removed from the lexer, and shall no
> longer have meaning in D:
>
>     <>
>     <>=
>     !<>
>     !<>=
>     !<
>     !<=
>     !>
>     !>=
>
> Rationale: Every class is now either completely ordered (if opCmp is
> supplied), or unordered (if not). If a class is completely ordered,
> then <> is the same as !=; !> is the same as <=, and so on.
> Conversely, if a class is unordered, then rule 4 makes all such
> comparisons compile-time errors. Consequently, there is no longer any
> need for these tests.
>
>
> ----
>
> How does all that sound?
>   
overall, I like your proposal - it even includes some of my ideas! :)
I still have however a few questions:
since the above "explicit" suggestion is slightly different from what I
thought, i still wonder about the following case:
class A {
    int number;
    ...
}
class B: A {
    string name;
    ...
}
A obj = new B;
A defines an explicit opCmp/opEquals as suggested above.
with the above suggestion the opCmp would accept the above "obj" ot type
B since it has static type A.
is the above behavior acceptable? I'm not sure.
for example, what if my code retrieves A's from a collection, so by
comparing only number and not name it can retrieve the wrong instance
from the collection(a real example would be a collection of widgets in a
GUI toolkit representing all the controls of a screen). the compiler
cannot check this at compile time so in order to prevent this the check
should happen at run-time and an exception should be thrown on error.

another issue is purely syntactical  - we seem to add more and more
keywords to D, and since IMO "explicit" should add runtime checks (not
compile checks as stated in the above suggestion) this "screams" to me
as a perfect case for annotations.
D really needs a standard facility to add user defined annotations
(metadata) to code and instead of adding more keywords to D, the above
"explicit" would be a perfect candidate to be provided by the standard
library. other annotations that could be also provided by the standard
library (I keep using this phrase since I prefer tango over phobos and
keep wishing for a merger) are "pure", "thread-safe", even "const" /
"invariant". less bloat for D more power to the programmer. the
annotation facility Ideally would provide hooks via an API to the
compiler so that annotations could be applied ar compile time by the
compiler (const) and/or runtime (explicit).

one last thing, adding a "comparable" interface/annotation could be
useful, so that AA's keys would need to implement this interface. of
course, all builtins would be comparable by default [except complex that
walter wants to move out of the language anyway....]
--Yigal



More information about the Digitalmars-d mailing list