Uniform Function Call Syntax(UFCS) and @property

kenji hara k.hara.pg at gmail.com
Mon Mar 7 15:43:27 PST 2011


2011/3/8 Steven Schveighoffer <schveiguy at yahoo.com>:
> 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.

On last version, f.read should be compile error. Foo.read forms a
single overload set, and module function forms another overload set.
The overlap generates error.

> An overload set is formed by a group of functions with the same name declared in the same scope.
http://www.digitalmars.com/d/2.0/hijack.html
This is the definition of overload set, therefore It is appropriate to
me that Foo.read and module function read form two overload set.

> 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).

Yes. In my opinnion, UFCS refuse hijacking based on overload set rule.

>>> 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().

Builtin property vs UFCS should generate error because it makes
overload set overlapping.

> 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.

Sorry, it does not work exactly same.
In my opinion:
1. File will be changed with range based design.
2. writef/writefln may replace using formattedWrite.
Then, File and formatting output will be following code:

File f;    // future: File will have output-range interface
f.formattedWrite("%s", ...);  // UFCS <- formattedWrite(f, "%s", ...);
// f.writef("%s", ...);   // naming may be replaced..?

This approach separate File I/O and string formatting preciously. And
`any` string output range can have formattedWrite/writef.

> 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.

This bad syntax should be refused. My first suggestion "Only the free
function its first parameter named 'this' can use with UFCS." achieve
this purpose.

I don't deny that the benefit of UFCS is only 'looks'. But natural
syntax has well worth for code reading. And UFCS is possible to add
'members' non-intrusive. If member name conflict occurs, overload set
rule make it an error.

Necessary to realize useful UFCS is appropriate annotations. 'named
this first parameter' is low cost, cachy, and doesn't need ugly
annotation syntax like @UFCS.

Kenji


More information about the Digitalmars-d mailing list