www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - possible "solution" for ufcs

reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
Someone wrote a very compelling argument for ufcs (uniform function call  
syntax) for ranges, and that is, given a slew of range functions, and a  
slew of ranges, it is nice to use a fluent programming syntax to specify  
wrappers for ranges without having to extend each range type.  For example:

take(10,stride(2,cycle([3,2,5,3])));

vs.

[3,2,5,3].cycle().stride(2).take(10);

And I thought damn it would be nice if ranges could implement ufcs, but  
other types that you didn't want to allow infinite extendability could  
avoid it.  That gave me an idea :)


import std.stdio;

struct ufcs
{
     auto opDispatch(string name, T...)(T args) // appropriate if compiles  
constraint here
     {
         mixin("return ." ~ name ~ "(this, args);");
     }
}

int foo(ufcs x, int y)
{
     writefln("it works! %d", y);
     return y+1;
}

void main()
{
     ufcs u;
     auto x = u.foo(1);
     assert(x == 2);
}

And it does indeed work (2.053)...

So we can have ufcs without any changes to the compiler, and we also make  
it a *choice* for people who don't want to allow infinite extendability,  
and don't want to deal with possible compiler ambiguities.

The opDispatch could even be a mixin itself (I think).

What do you think?

-Steve
Jun 06 2011
next sibling parent reply Monkol <dj_mon mail.ru> writes:
On Mon, 06 Jun 2011 22:00:13 +0300, Steven Schveighoffer  
<schveiguy yahoo.com> wrote:

 Someone wrote a very compelling argument for ufcs (uniform function call  
 syntax) for ranges, and that is, given a slew of range functions, and a  
 slew of ranges, it is nice to use a fluent programming syntax to specify  
 wrappers for ranges without having to extend each range type.  For  
 example:

 take(10,stride(2,cycle([3,2,5,3])));

 vs.

 [3,2,5,3].cycle().stride(2).take(10);

 And I thought damn it would be nice if ranges could implement ufcs, but  
 other types that you didn't want to allow infinite extendability could  
 avoid it.  That gave me an idea :)


 import std.stdio;

 struct ufcs
 {
      auto opDispatch(string name, T...)(T args) // appropriate if  
 compiles constraint here
      {
          mixin("return ." ~ name ~ "(this, args);");
      }
 }

 int foo(ufcs x, int y)
 {
      writefln("it works! %d", y);
      return y+1;
 }

 void main()
 {
      ufcs u;
      auto x = u.foo(1);
      assert(x == 2);
 }

 And it does indeed work (2.053)...

 So we can have ufcs without any changes to the compiler, and we also  
 make it a *choice* for people who don't want to allow infinite  
 extendability, and don't want to deal with possible compiler ambiguities.

 The opDispatch could even be a mixin itself (I think).

 What do you think?

 -Steve

may be you lost symbol "!" when call template opDispatch!(string name, T...)(T args)
Jun 06 2011
parent KennyTM~ <kennytm gmail.com> writes:
On Jun 7, 11 03:22, Monkol wrote:
 On Mon, 06 Jun 2011 22:00:13 +0300, Steven Schveighoffer
 <schveiguy yahoo.com> wrote:

 Someone wrote a very compelling argument for ufcs (uniform function
 call syntax) for ranges, and that is, given a slew of range functions,
 and a slew of ranges, it is nice to use a fluent programming syntax to
 specify wrappers for ranges without having to extend each range type.
 For example:

 take(10,stride(2,cycle([3,2,5,3])));

 vs.

 [3,2,5,3].cycle().stride(2).take(10);

 And I thought damn it would be nice if ranges could implement ufcs,
 but other types that you didn't want to allow infinite extendability
 could avoid it. That gave me an idea :)


 import std.stdio;

 struct ufcs
 {
 auto opDispatch(string name, T...)(T args) // appropriate if compiles
 constraint here
 {
 mixin("return ." ~ name ~ "(this, args);");
 }
 }

 int foo(ufcs x, int y)
 {
 writefln("it works! %d", y);
 return y+1;
 }

 void main()
 {
 ufcs u;
 auto x = u.foo(1);
 assert(x == 2);
 }

 And it does indeed work (2.053)...

 So we can have ufcs without any changes to the compiler, and we also
 make it a *choice* for people who don't want to allow infinite
 extendability, and don't want to deal with possible compiler ambiguities.

 The opDispatch could even be a mixin itself (I think).

 What do you think?

 -Steve

may be you lost symbol "!" when call template opDispatch!(string name, T...)(T args)

No Steven is correct. You don't need the '!' before the template parameters.
Jun 06 2011
prev sibling next sibling parent reply so <so so.so> writes:
I think something is missing here.

It doesn't convert fun(int, ufcs) to ufcs.fun(int) as in the examples.
It converts fun(ufcs, int) to ufcs.fun(int).

We need is a solution to this:

fun(T)(arg1, ... ufcs!T, ... argN)
Jun 06 2011
parent KennyTM~ <kennytm gmail.com> writes:
On Jun 7, 11 03:23, so wrote:
 I think something is missing here.

 It doesn't convert fun(int, ufcs) to ufcs.fun(int) as in the examples.
 It converts fun(ufcs, int) to ufcs.fun(int).

 We need is a solution to this:

 fun(T)(arg1, ... ufcs!T, ... argN)

auto ref opDispatch(string name, T...)(auto ref T args) { mixin("alias ." ~ name ~ " f;"); alias ParameterTypeTuple!f Params; enum i = staticIndexOf!(Unqual!(typeof(this)), staticMap!(Unqual, Params)); static assert(i >= 0); return f(args[0 .. i], this, args[i .. $]); } (Doesn't work with overload set though.)
Jun 06 2011
prev sibling next sibling parent reply KennyTM~ <kennytm gmail.com> writes:
On Jun 7, 11 03:00, Steven Schveighoffer wrote:
 Someone wrote a very compelling argument for ufcs (uniform function call
 syntax) for ranges, and that is, given a slew of range functions, and a
 slew of ranges, it is nice to use a fluent programming syntax to specify
 wrappers for ranges without having to extend each range type. For example:

 take(10,stride(2,cycle([3,2,5,3])));

 vs.

 [3,2,5,3].cycle().stride(2).take(10);

 And I thought damn it would be nice if ranges could implement ufcs, but
 other types that you didn't want to allow infinite extendability could
 avoid it. That gave me an idea :)


 import std.stdio;

 struct ufcs
 {
 auto opDispatch(string name, T...)(T args) // appropriate if compiles
 constraint here
 {
 mixin("return ." ~ name ~ "(this, args);");
 }
 }

 int foo(ufcs x, int y)
 {
 writefln("it works! %d", y);
 return y+1;
 }

 void main()
 {
 ufcs u;
 auto x = u.foo(1);
 assert(x == 2);
 }

 And it does indeed work (2.053)...

 So we can have ufcs without any changes to the compiler, and we also
 make it a *choice* for people who don't want to allow infinite
 extendability, and don't want to deal with possible compiler ambiguities.

 The opDispatch could even be a mixin itself (I think).

 What do you think?

 -Steve

Maybe better auto ref opDispatch(string name, T...)(auto ref T args) { mixin("return ." ~ name ~ "(this, args);"); } so that ref-returns and ref-parameters can be handled as well. Doesn't work for 'lazy' though. It also cannot preserve 'pure'-ity, 'nothrow'-ness and ' safe'-ty of the original function.
Jun 06 2011
parent Timon Gehr <timon.gehr gmx.ch> writes:
KennyTM~ wrote:
 Maybe better

      auto ref opDispatch(string name, T...)(auto ref T args) {
          mixin("return ." ~ name ~ "(this, args);");
      }

 so that ref-returns and ref-parameters can be handled as well. Doesn't
 work for 'lazy' though. It also cannot preserve 'pure'-ity,
 'nothrow'-ness and ' safe'-ty of the original function.

Yes, but this is an unresolved problem of the language in general. We can have the template mixin just mixin all 8 versions with different template constraints until that gets fixed (thats what will be done anyways, at least purity is reflected in the mangled name AFAIK). The most important feature needed to make it work correctly, "auto ref" is already there. Timon
Jun 06 2011
prev sibling next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
Steven Schveighoffer wrote:
 Someone wrote a very compelling argument for ufcs (uniform function call
 syntax) for ranges, and that is, given a slew of range functions, and a
 slew of ranges, it is nice to use a fluent programming syntax to specify
 wrappers for ranges without having to extend each range type.  For example:

 take(10,stride(2,cycle([3,2,5,3])));

 vs.

 [3,2,5,3].cycle().stride(2).take(10);

 And I thought damn it would be nice if ranges could implement ufcs, but
 other types that you didn't want to allow infinite extendability could
 avoid it.  That gave me an idea :)


 import std.stdio;

 struct ufcs
 {
      auto opDispatch(string name, T...)(T args) // appropriate if compiles
 constraint here
      {
          mixin("return ." ~ name ~ "(this, args);");
      }
 }

 int foo(ufcs x, int y)
 {
      writefln("it works! %d", y);
      return y+1;
 }

 void main()
 {
      ufcs u;
      auto x = u.foo(1);
      assert(x == 2);
 }

 And it does indeed work (2.053)...

 So we can have ufcs without any changes to the compiler, and we also make
 it a *choice* for people who don't want to allow infinite extendability,
 and don't want to deal with possible compiler ambiguities.

 The opDispatch could even be a mixin itself (I think).

 What do you think?

 -Steve

Great! =) This resolves everything around UFCS! Why has nobody come up with this before? You should definitely file an enhancement request for phobos. We just add something like this somewhere: mixin template implementUFCS() { auto opDispatch(string name, T...)(T args) if(is(typeof({mixin("return ." ~ name ~ "(this, args);");}))){ mixin("return ." ~ name ~ "(this, args);"); } } Each range type will do mixin implementUFCS; And we'll have optional UFCS!!! A little drawback: Types using implementUFCS will have very bad error reporting if somebody types a member name wrong: struct foo{mixin implementUFCS;} int main(){foo x;x.bar();} Error: template instance opDispatch!("bar") does not match template declaration opDispatch(string name,T...) if (is(typeof(delegate () { mixin("return ." ~ name ~ "(this, args);"); } ))) I think here a change to the compiler would be appropriate at some point so that we can get: Error: no property 'bar' for type 'foo' If opDispatch does not match. But this is definitely the way to go for UFCS! Timon
Jun 06 2011
prev sibling next sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2011-06-06 15:00:13 -0400, "Steven Schveighoffer" 
<schveiguy yahoo.com> said:

 And it does indeed work (2.053)...
 
 So we can have ufcs without any changes to the compiler, and we also 
 make  it a *choice* for people who don't want to allow infinite 
 extendability,  and don't want to deal with possible compiler 
 ambiguities.
 
 The opDispatch could even be a mixin itself (I think).
 
 What do you think?

Clever. But how does it work for properties? Pure/safe/nothrow functions? Ref and out parameters? Note that properties specifically are already problem for the compiler-implemented array-member syntax. Bottom line: there's a lot of work to do to make UFCS work right. And it'll require some language-level changes anyway if we want it to work right. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Jun 06 2011
parent Timon Gehr <timon.gehr gmx.ch> writes:
Steven Schveighoffer wrote:
 Yes, but I also think if we can make UFCS optional, we give some power
 back to the author of the struct/class.  He can choose not to participate
 in ufcs, and then he can control the API to his type.

 Plus, this can be a good interim step!  It works right now!

 -Steve

Just realized that actually it doesn't. It does not work across module boundaries... =/ Timon
Jun 06 2011
prev sibling next sibling parent Monkol <dj_mon mail.ru> writes:
On Mon, 06 Jun 2011 22:00:13 +0300, Steven Schveighoffer  
<schveiguy yahoo.com> wrote:

 Someone wrote a very compelling argument for ufcs (uniform function call  
 syntax) for ranges, and that is, given a slew of range functions, and a  
 slew of ranges, it is nice to use a fluent programming syntax to specify  
 wrappers for ranges without having to extend each range type.  For  
 example:

 take(10,stride(2,cycle([3,2,5,3])));

 vs.

 [3,2,5,3].cycle().stride(2).take(10);

 And I thought damn it would be nice if ranges could implement ufcs, but  
 other types that you didn't want to allow infinite extendability could  
 avoid it.  That gave me an idea :)


 import std.stdio;

 struct ufcs
 {
      auto opDispatch(string name, T...)(T args) // appropriate if  
 compiles constraint here
      {
          mixin("return ." ~ name ~ "(this, args);");
      }
 }

 int foo(ufcs x, int y)
 {
      writefln("it works! %d", y);
      return y+1;
 }

 void main()
 {
      ufcs u;
      auto x = u.foo(1);
      assert(x == 2);
 }

 And it does indeed work (2.053)...

 So we can have ufcs without any changes to the compiler, and we also  
 make it a *choice* for people who don't want to allow infinite  
 extendability, and don't want to deal with possible compiler ambiguities.

 The opDispatch could even be a mixin itself (I think).

 What do you think?

 -Steve

what this code must to do?
Jun 06 2011
prev sibling next sibling parent reply Monkol <dj_mon mail.ru> writes:
On Mon, 06 Jun 2011 22:00:13 +0300, Steven Schveighoffer  
<schveiguy yahoo.com> wrote:

 Someone wrote a very compelling argument for ufcs (uniform function call  
 syntax) for ranges, and that is, given a slew of range functions, and a  
 slew of ranges, it is nice to use a fluent programming syntax to specify  
 wrappers for ranges without having to extend each range type.  For  
 example:

 take(10,stride(2,cycle([3,2,5,3])));

 vs.

 [3,2,5,3].cycle().stride(2).take(10);

 And I thought damn it would be nice if ranges could implement ufcs, but  
 other types that you didn't want to allow infinite extendability could  
 avoid it.  That gave me an idea :)


 import std.stdio;

 struct ufcs
 {
      auto opDispatch(string name, T...)(T args) // appropriate if  
 compiles constraint here
      {
          mixin("return ." ~ name ~ "(this, args);");
      }
 }

 int foo(ufcs x, int y)
 {
      writefln("it works! %d", y);
      return y+1;
 }

 void main()
 {
      ufcs u;
      auto x = u.foo(1);
      assert(x == 2);
 }

 And it does indeed work (2.053)...

 So we can have ufcs without any changes to the compiler, and we also  
 make it a *choice* for people who don't want to allow infinite  
 extendability, and don't want to deal with possible compiler ambiguities.

 The opDispatch could even be a mixin itself (I think).

 What do you think?

 -Steve

what this code must to do?
Jun 06 2011
parent Mafi <mafi example.org> writes:
Am 06.06.2011 21:38, schrieb Monkol:
 On Mon, 06 Jun 2011 22:00:13 +0300, Steven Schveighoffer
 <schveiguy yahoo.com> wrote:

 Someone wrote a very compelling argument for ufcs (uniform function
 call syntax) for ranges, and that is, given a slew of range functions,
 and a slew of ranges, it is nice to use a fluent programming syntax to
 specify wrappers for ranges without having to extend each range type.
 For example:

 take(10,stride(2,cycle([3,2,5,3])));

 vs.

 [3,2,5,3].cycle().stride(2).take(10);

 And I thought damn it would be nice if ranges could implement ufcs,
 but other types that you didn't want to allow infinite extendability
 could avoid it. That gave me an idea :)


 import std.stdio;

 struct ufcs
 {
 auto opDispatch(string name, T...)(T args) // appropriate if compiles
 constraint here
 {
 mixin("return ." ~ name ~ "(this, args);");
 }
 }

 int foo(ufcs x, int y)
 {
 writefln("it works! %d", y);
 return y+1;
 }

 void main()
 {
 ufcs u;
 auto x = u.foo(1);
 assert(x == 2);
 }

 And it does indeed work (2.053)...

 So we can have ufcs without any changes to the compiler, and we also
 make it a *choice* for people who don't want to allow infinite
 extendability, and don't want to deal with possible compiler ambiguities.

 The opDispatch could even be a mixin itself (I think).

 What do you think?

 -Steve

what this code must to do?

opDispatch is special template member a class or struct can have. When using the dot-operator to acces members and these aren't there, opDispatch!"memmbername" is tried. Now when ou try to use uniform function call syntax (UFC) on a struct, the function-as-method you want to use is not in the struct but in the module-scope. The compiler therefore can't find any memeber and tries opDispatch which itself tries to resolve to a module-scope function using the leading dot. Through ct-string-operations and mixin the name of the function gets injected into the code. I hope I could help you. Mafi
Jun 06 2011
prev sibling next sibling parent Torarin <torarind gmail.com> writes:
2011/6/6 KennyTM~ <kennytm gmail.com>:
 Maybe better

 =A0 =A0auto ref opDispatch(string name, T...)(auto ref T args) {
 =A0 =A0 =A0 =A0mixin("return ." ~ name ~ "(this, args);");
 =A0 =A0}

 so that ref-returns and ref-parameters can be handled as well. Doesn't wo=

 for 'lazy' though. It also cannot preserve 'pure'-ity, 'nothrow'-ness and
 ' safe'-ty of the original function.

As far as I understand, auto ref is supposed to mean "always ref, and accept r-values". So we would need to allow tuples to contain ref types and construct it from the target function. Torarin
Jun 06 2011
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 06 Jun 2011 15:32:38 -0400, Michel Fortin  
<michel.fortin michelf.com> wrote:

 On 2011-06-06 15:00:13 -0400, "Steven Schveighoffer"  
 <schveiguy yahoo.com> said:

 And it does indeed work (2.053)...
  So we can have ufcs without any changes to the compiler, and we also  
 make  it a *choice* for people who don't want to allow infinite  
 extendability,  and don't want to deal with possible compiler  
 ambiguities.
  The opDispatch could even be a mixin itself (I think).
  What do you think?

Clever. But how does it work for properties? Pure/safe/nothrow functions? Ref and out parameters? Note that properties specifically are already problem for the compiler-implemented array-member syntax.

I think properties can be done, but we can't do it by marking the function property (which would mean something different to the compiler). Something like this might work: struct ufcs { property auto opDispatch(string s)() // TODO: appropriate constraint { mixin("return ."prop_" ~ s ~ "(this);"); } property auto opDispatch(string s, T)(T arg) // TODO: appropriate constraint { mixin("return ."prop_" ~ s ~ "(this, arg);"); } } It's very sketchy, you need to appropriately name your global functions, because naming them with property doesn't really work. Custom annotations could work well here. As far as pure/safe/nothrow, I think templates need general work in this area anyways. IFTI in particular is woefully inadequate for wrapping functions. I've proposed an enhancement to assist in this in some cases, perhaps it can be tweaked to allow all sorts of "forwarding" behavior. Essentially, I think you should be able to use another function to guide IFTI in deciding what parameter types to use. http://d.puremagic.com/issues/show_bug.cgi?id=4998
 Bottom line: there's a lot of work to do to make UFCS work right. And  
 it'll require some language-level changes anyway if we want it to work  
 right.

Yes, but I also think if we can make UFCS optional, we give some power back to the author of the struct/class. He can choose not to participate in ufcs, and then he can control the API to his type. Plus, this can be a good interim step! It works right now! -Steve
Jun 06 2011
prev sibling next sibling parent reply Lutger Blijdestijn <lutger.blijdestijn gmail.com> writes:
Steven Schveighoffer wrote:

 Someone wrote a very compelling argument for ufcs (uniform function call
 syntax) for ranges, and that is, given a slew of range functions, and a
 slew of ranges, it is nice to use a fluent programming syntax to specify
 wrappers for ranges without having to extend each range type.  For
 example:
 
 take(10,stride(2,cycle([3,2,5,3])));
 
 vs.
 
 [3,2,5,3].cycle().stride(2).take(10);
 
 And I thought damn it would be nice if ranges could implement ufcs, but
 other types that you didn't want to allow infinite extendability could
 avoid it.  That gave me an idea :)
 
 
 import std.stdio;
 
 struct ufcs
 {
      auto opDispatch(string name, T...)(T args) // appropriate if compiles
 constraint here
      {
          mixin("return ." ~ name ~ "(this, args);");
      }
 }
 
 int foo(ufcs x, int y)
 {
      writefln("it works! %d", y);
      return y+1;
 }
 
 void main()
 {
      ufcs u;
      auto x = u.foo(1);
      assert(x == 2);
 }
 
 And it does indeed work (2.053)...
 
 So we can have ufcs without any changes to the compiler, and we also make
 it a *choice* for people who don't want to allow infinite extendability,
 and don't want to deal with possible compiler ambiguities.
 
 The opDispatch could even be a mixin itself (I think).
 
 What do you think?
 
 -Steve

Nice and clever! However, I don't think it's good as an alternative for ufcs in the language, unless that is going to be dropped because of too many ambiguities. ufcs should imho be a decision on the caller side or the side of the function extending a datatype (like extension methods in C#), not the implementor of a datatype. It should ideally be available out of the box, to be used with any type.
Jun 06 2011
parent Lutger Blijdestijn <lutger.blijdestijn gmail.com> writes:
Steven Schveighoffer wrote:

 On Mon, 06 Jun 2011 17:07:25 -0400, Lutger Blijdestijn
 <lutger.blijdestijn gmail.com> wrote:
 
 Nice and clever! However, I don't think it's good as an alternative for
 ufcs
 in the language, unless that is going to be dropped because of too many
 ambiguities.

I admit I'm not warm and fuzzy on arbitrarily extending interface. For one, having functions in one place makes docs/members easy to look up. i.e. I can look at one file and know all the operations for a datatype, and I can look at a function call and know where to find it in the documentation. I'm a firm believer that an object's author should be in charge of the interface to his object. Hence my position on enforcing strict property syntax.

I understand. What I find most attractive about ufcs is not really about extending interfaces, but making function composition saner to read. In some libraries such as jquery and linq it's very clean to compose operations, because they work well with chaining. Although it's not the same, something similar (and actually much better) could be achieved with ranges + ufcs. Ranges want to be composed, yet I find myself often introducing temporary variables to avoid a mess of nesting. Libraries with chaining are more pleasant, imho. I'll readily believe that the problems are significant though. Another issue may be function hijacking: when I call bar.foo() where foo is my extension, and the author of bar adds a foo method to it's type, the behavior silently changes. Your proposal does give some control over that.
 However, ranges are really not in that league -- all operations on ranges
 are pretty much external.  Only the primitives live in the type.  This is
 something that is accepted and understood.  The same thing with slices.
 So it's natural to want to use ufcs there.

To me, this would be a big improvement on the usability of ranges. Not saying ranges aren't usable, but this would make for much shorter and readable code.
 BTW, we've had several people who think capacity should be in std.array,
 but it is actually in core.object, this is due to the disjoint nature of
 slice members -- they live everywhere.  This will only get worse.
 
 ufcs should imho be a decision on the caller side or the side of the
 function extending a datatype (like extension methods in C#), not the
 implementor of a datatype. It should ideally be available out of the
 box, to
 be used with any type.

I am OK with ucfs being available everywhere, but the ambiguities are difficult to work around. Especially for properties. My preference is that UFCS be supported only for builtin language types (slices, fixed-size arrays, primitives) because there is no other option for extension, and we should work on allowing author-sponsored extendability in custom types. Type wrapping in general needs a lot more support from the compiler and IFTI. The author can even explicitly only allow extendability of certain function names, with the ability to use template constraints on opDispatch. I just think it's an area we *already* need to work on, and given this solution, it naturally allows better ufcs as you can wrap functions better. -Steve

Jun 06 2011
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 06 Jun 2011 17:07:25 -0400, Lutger Blijdestijn  
<lutger.blijdestijn gmail.com> wrote:

 Nice and clever! However, I don't think it's good as an alternative for  
 ufcs
 in the language, unless that is going to be dropped because of too many
 ambiguities.

I admit I'm not warm and fuzzy on arbitrarily extending interface. For one, having functions in one place makes docs/members easy to look up. i.e. I can look at one file and know all the operations for a datatype, and I can look at a function call and know where to find it in the documentation. I'm a firm believer that an object's author should be in charge of the interface to his object. Hence my position on enforcing strict property syntax. However, ranges are really not in that league -- all operations on ranges are pretty much external. Only the primitives live in the type. This is something that is accepted and understood. The same thing with slices. So it's natural to want to use ufcs there. BTW, we've had several people who think capacity should be in std.array, but it is actually in core.object, this is due to the disjoint nature of slice members -- they live everywhere. This will only get worse.
 ufcs should imho be a decision on the caller side or the side of the
 function extending a datatype (like extension methods in C#), not the
 implementor of a datatype. It should ideally be available out of the  
 box, to
 be used with any type.

I am OK with ucfs being available everywhere, but the ambiguities are difficult to work around. Especially for properties. My preference is that UFCS be supported only for builtin language types (slices, fixed-size arrays, primitives) because there is no other option for extension, and we should work on allowing author-sponsored extendability in custom types. Type wrapping in general needs a lot more support from the compiler and IFTI. The author can even explicitly only allow extendability of certain function names, with the ability to use template constraints on opDispatch. I just think it's an area we *already* need to work on, and given this solution, it naturally allows better ufcs as you can wrap functions better. -Steve
Jun 06 2011
prev sibling next sibling parent reply "Nick Sabalausky" <a a.a> writes:
-- 

-------------------------------
Not sent from an iPhone.

"Steven Schveighoffer" <schveiguy yahoo.com> wrote in message 
news:op.vwn4enw5eav7ka localhost.localdomain...
 Someone wrote a very compelling argument for ufcs (uniform function call 
 syntax) for ranges, and that is, given a slew of range functions, and a 
 slew of ranges, it is nice to use a fluent programming syntax to specify 
 wrappers for ranges without having to extend each range type.  For 
 example:

 take(10,stride(2,cycle([3,2,5,3])));

 vs.

 [3,2,5,3].cycle().stride(2).take(10);

 And I thought damn it would be nice if ranges could implement ufcs, but 
 other types that you didn't want to allow infinite extendability could 
 avoid it.  That gave me an idea :)


 import std.stdio;

 struct ufcs
 {
     auto opDispatch(string name, T...)(T args) // appropriate if compiles 
 constraint here
     {
         mixin("return ." ~ name ~ "(this, args);");
     }
 }

 int foo(ufcs x, int y)
 {
     writefln("it works! %d", y);
     return y+1;
 }

 void main()
 {
     ufcs u;
     auto x = u.foo(1);
     assert(x == 2);
 }

 And it does indeed work (2.053)...

 So we can have ufcs without any changes to the compiler, and we also make 
 it a *choice* for people who don't want to allow infinite extendability, 
 and don't want to deal with possible compiler ambiguities.

 The opDispatch could even be a mixin itself (I think).

 What do you think?

I just hope it doesn't cause real ufcs to become an even lower priority than it already is.
Jun 06 2011
parent "Nick Sabalausky" <a a.a> writes:
"Nick Sabalausky" <a a.a> wrote in message 
news:isjhv6$309k$1 digitalmars.com...
 "Steven Schveighoffer" <schveiguy yahoo.com> wrote in message 
 news:op.vwn4enw5eav7ka localhost.localdomain...
 Someone wrote a very compelling argument for ufcs (uniform function call 
 syntax) for ranges, and that is, given a slew of range functions, and a 
 slew of ranges, it is nice to use a fluent programming syntax to specify 
 wrappers for ranges without having to extend each range type.  For 
 example:

 take(10,stride(2,cycle([3,2,5,3])));

 vs.

 [3,2,5,3].cycle().stride(2).take(10);

 And I thought damn it would be nice if ranges could implement ufcs, but 
 other types that you didn't want to allow infinite extendability could 
 avoid it.  That gave me an idea :)

I just hope it doesn't cause real ufcs to become an even lower priority than it already is.

Sorry for the partial top-posting...
Jun 06 2011
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 06 Jun 2011 17:18:27 -0400, Timon Gehr <timon.gehr gmx.ch> wrote:

 Steven Schveighoffer wrote:
 Yes, but I also think if we can make UFCS optional, we give some power
 back to the author of the struct/class.  He can choose not to  
 participate
 in ufcs, and then he can control the API to his type.

 Plus, this can be a good interim step!  It works right now!

 -Steve

Just realized that actually it doesn't. It does not work across module boundaries... =/

:( Hm... this makes sense, the template is instantiated at the context level of the defining module, not the calling module. It can however work for something like std.range calling std.range or std.algorithm functions. Just not in the general sense. -Steve
Jun 06 2011