On Mon, 07 Mar 2011 13:28:26 -0500, kenji hara <k.hara...@gmail.com> wrote:
The problem is when there is a conflict between an actual member
funtion,
and a free function which takes the type as its first argument.
Who wins? The obvious choice is the member function. But let's say the
member function is added to the type long after the external function
exists. People who have written code that uses the external function
via
UFCS all of a sudden are now calling the member function, without
warning.
This is a form of hijacking.
Why do you prefer module functions? It will be selected by UFCS only
when there is no member that has the same name in class/struct. It is
clear.
I'll give you an example. This assumes that the compiler silently prefers
member functions over free ones.
Say you are using a struct from a library you didn't write like this:
struct Foo
{
int x;
}
And you want to read data into it.
So you write your own function:
void read(ref Foo f) { readf("%d", &f.x); }
So now, you can write stuff like:
Foo f;
f.read();
and things work great!
But a few years later, the author of foo decides to add serialization to
Foo, and does:
struct Foo
{
int x;
void read() { readf("%x", &f.x); }
void write() { writef("%x", f.x); }
}
All of a sudden, all the code you had that did f.read() now calls the
member, which does something subtly different.
You could say an ambiguous case should be an error, but then someone is
able
to issue a sort of "denial of service" attack by simply defining a free
function which coincides with a member function.
I don't understand the meaning of "denial of service" what you say. Is
it causing compile error? It is same behavior with overload set
conflict.
If the ambiguity is simply flagged as an error by the compiler, by simply
defining a function with the same name outside the class or struct, you
can deny access to the member function.
Following the above example, if my localized read(ref Foo f) is defined in
a common library module, anyone who uses my code (but not my read
function) now wants to use the new Foo.read member function, it always
results in a compile error. In essence, there is actually no way to
access that member unless you stop importing my module. Maybe I could
care less that it's a problem you have, I've since changed all my code to
read(f) instead of f.read() to workaround my problem.
These problems allow either hijacking of a function, or denying an object
it's basic ability to define new members that are specific to itself. For
all this confusion and pain, I think it's not worth the benefit of being
able to do f.read() vs. read(f).
With builtins, there is no conflict. You cannot define member
functions for
integers or for arrays, so the ambiguity is gone.
Almost yes. (Builtin array has 'length' property. It may conflict
module function 'length'.)
Yes, but those are builtin properties, disallowed from day 1.
Essentially, there is no way for an ambiguity to 'creep' into the code,
since you could never call your length function via arr.length().
I see almost no benefit for having UFCS on classes and structs. It is
already possible to add member functions to those types, and the
difference
between x(y) and y.x() is extremely trivial. If anything, this makes
member
functions lose their specificity -- a member function is no longer
attributed to the struct or class, it can be defined anywhere.
UFCS is veri usuful. It reduce fat interfaces of class/struct, and
separate 'useful but surrounded operation' from core operations of the
object.
A simple example is std.stdio.File.writefln. This function does the
almost same job std.stdio.writefln. Correct UFCS will cut wastes like
this.
std.stdio.writefln forwards *directly* to stdout.writefln. Boilerplate
code like this should be inlined out. I see zero waste here.
Actually, the way writefln works, I think you picked an example that would
not be affected by UFCS. writefln without a File argument would still
need to exist in order to select stdout as the destination.
But in any case, the difference between calling a free function and a
member is so small that there really is no gain. You want to define a
function that works on multiple types? It works, you just have to call
func(t, ...) instead of t.func(...). I'd rather keep member functions as
members, and external functions as external functions, as it adds to the
understanding/readability.
And the potential for abuse grows.
For example, this should probably work today (not tested):
"%d".writefln(5);
And with UFCS, I now have a slew of types which allow this kind of abuse.
-Steve