UFCS for D

Steven Schveighoffer schveiguy at yahoo.com
Thu Mar 29 16:34:00 PDT 2012


On Wed, 28 Mar 2012 21:53:57 -0400, Jesse Phillips  
<jessekphillips+D at gmail.com> wrote:

> I won't be going out of my way to check this, but there is a mention of  
> adding the range primatives. This works, but it doesn't make the class a  
> range for any other module, so std.algorithms won't recogonise it as a  
> range.

At first thought, I believed this should be fixable -- if not working  
already.  Consider that std.algorithm doesn't include *your* module, yet  
you can pass types defined in your module into std.algorithm and it works  
just fine.

But I realized after typing about 2 messages in response to this (and  
deleting them), you are right, there is a fundamental problem here.   
Because the template instantiation is based solely on the type.  It does  
*not* include the type and whatever other modules you may have included  
that could define extension methods.  I don't think it's an implementation  
issue, I think it's a design issue -- there simply is no way to do this.

A counter case:

module1.d:

int foo(T)(T t)
{
    return t.bar();
}

module2.d:

struct S { int x;}

module3.d:

import module1, module2;

int bar(S s) { return s.x * 2;}

void baz1()
{
    S s(2);
    assert(foo(s) == 4);
}

module4.d:

import module1, module 2;

int bar(S s) { return s.x * 3;}

void baz2()
{
    S s(2);
    assert(foo(s) == 6);
}

// and to drive the point further:
module5.d:
import module3, module4;

void main()
{
    baz1();
    baz2();
}

In order for the asserts to *both* pass, there has to be two different  
instantiations of foo!S, one for module3, and one for module4.

So two possible sane rules:
1. A template instantiation can *only* use UFCS from functions defined in  
or imported from the module in which the template is defined. (i.e. the  
way it works now I think)
or
2. A template instantiation can *only* use UFCS from functions defined in  
or imported from the module in which the template is defined, *and* from  
functions as defined or imported by the module that defines the type on  
which UFCS is being used.  In other words, from my example above, only  
functions defined in or imported from module1.d and module2.d.  Therefore,  
the bar extension defined in module3 and module4 cannot be called from  
module1.

For builtin types (such as arrays or numbers), there wouldn't be a module  
that the type was defined.  However, object.di is imported by everything,  
so extensions could be put in there.

This kind of puts a damper on certain expectations for how UFCS could be  
used.  But I don't see any other way (other than adding the current module  
into the instantiation somehow -- imagine the template bloat...).

Even with this limitation, UFCS still allows a lot of cool things.  One  
misleading suggestion from the article however, it's not very easy to  
create non-friend non-member functions using UFCS, considering that every  
function in a given module is a friend.  In order to do this, you would  
need a helper module for each module that wants to define such non-friend  
functions.  Given the above proof, the helper module would also have to be  
imported by the main module.

-Steve


More information about the Digitalmars-d-announce mailing list