A Small Contribution to Phobos

Jonathan M Davis jmdavisProg at gmx.com
Sat Jun 1 21:09:56 PDT 2013


On Sunday, June 02, 2013 04:57:53 Meta wrote:
> I saw a thread a few days ago about somebody wanting a few
> UFCS-based convenience functions, so I thought that I'd take the
> opportunity to make a small contribution to Phobos. Currently I
> have four small functions: each, exhaust, perform, and tap, and
> would like some feedback.
> 
> each is designed to perform operations with side-effects on each
> range element. To actually change the elements of the range, each
> element must be accepted by reference.
> 
> Range each(alias fun, Range)(Range r)
> if (isInputRange!(Unqual!Range))
> {
>      alias unaryFun!fun _fun;
>      foreach (ref e; r)
>      {
>          fun(e);
>      }
> 
>      return r;
> }
> 
> //Prints [-1, 0, 1]
> [1, 2, 3].each!((ref i) => i -= 2).writeln;

For reference type ranges and input ranges which are not forward ranges, this 
will consume the range and return nothing. It would have to accept only 
forward ranges and save the result before iterating over it. Also, range-based 
functions should not be strict (i.e. not lazy) without good reason. And I 
don't see much reason to make this strict. Also, it's almost the same thing as 
map. Why not just use map? The predicate can simply return the same value 
after it's operated on it.

If we did add this, I'd argue that transform is a better name, but I'm still 
inclined to think that it's not worth adding.

> exhaust iterates a range until it is exhausted. It also has the
> nice feature that if range.front is callable, exhaust will call
> it upon each iteration.
> 
> Range exhaust(Range)(Range r)
> if (isInputRange!(Unqual!Range))
> {
> 
>      while (!r.empty)
>      {
>          r.front();
>          r.popFront();
>      }
> 
>      return r;
> }
> 
> //Writes "www.dlang.org". x is an empty MapResult range.
> auto x = "www.dlang.org"
>           .map!((c) { c.write; return false; })
>           .exhaust;
> 
> //Prints []
> [1, 2, 3].exhaust.writeln;

The callable bit won't work. It'll just call front. You'd have to do something 
like

static if(isCallable!(ElementType!R))
    r.front()();

Also, if front were pure, then calling it and doing nothing with its return 
value would result in a compilation error. The same goes if the element type 
is a pure callable. And even if this did work exactly as you intended. I think 
that assuming that someone exhausting the range would would what front returns 
to be called is a bad idea. Maybe they do, maybe they don't, I'd expect that 
in most cases, they wouldn't. If that's what they want, they can call map 
before calling exhaust.

> perform is pretty badly named, but I couldn't come up with a
> better one. It can be inserted in a UFCS chain and perform some
> operation with side-effects. It doesn't alter its argument, just
> returns it for the next function in the chain.
> 
> T perform(alias dg, T)(ref T val)
> {
>      dg();
> 
>      return val;
> }
> 
> //Prints "Mapped: 2 4"
> [1, 2, 3, 4, 5]
> .filter!(n => n < 3)
> .map!(n => n * n)
> .perform!({write("Mapped: ");})
> .each!(n => write(n, " "));

So, you want to have a function which you pass something (including a range) 
and then returns that same value after calling some other function? Does this 
really buy you much over just splitting up the expression - you're already 
giving a multline example anyway.

auto foo = [1, 2, 3, 4, 4].filt!(n => n < 3)().map!(n => n * n)();
write("Mapped: ");
foo.each!(n => write(n, "")();

And I think that this is a perfect example of something that should just be 
done with foreach anyway. Not to mention, if you're calling very many 
functions, you're going to need to use multiple lines, in which case chaining 
the functions like that doesn't buy you much. All you end up doing is taking 
what would normally be a sequence of statements and turned it into one 
multiline statement. I don't think that this buys us much, especially when 
it's just calling one function which does nothing on any object in the chain.

> Lastly is tap, which takes a value and performs some mutating
> operation on it. It then returns the value.
> 
> T tap(alias dg, T)(auto ref T val)
> {
>      dg(val);
> 
>      return val;
> }
> 
> class Foo
> {
>      int x;
>      int y;
> }
> 
> auto f = (new Foo).tap!((f)
> {
>      f.x = 2;
>      f.y = 3;
> });
> 
> //Prints 2 3
> writeln(f.x, " ", f.y);
> 
> struct Foo2
> {
>      int x;
>      int y;
> }
> 
> //Need to use ref for value types
> auto f2 = Foo2().tap!((ref f)
> {
>      f.x = 3;
>      f.y = 2;
> });
> 
> //Prints 3 2
> writeln(f2.x, " ", f2.y);

Why do you need tap? So that you can use an anonymous function? If it had a 
name, you'd just use it with UFCS. I'd argue that this use case is minimal 
enough that you might as well just give it a name and then use UFCS if you 
really want to use UFCS, and if you want an anonymous function, what's the 
real gain of chaining it with UFCS anyway? It makes the expression much harder 
to read if you try and chain calls on the anonymous function.

UFCS' main purpose is making it so that a function can be called on multiple 
types in the same manner (particularly where it could be a member function in 
some cases and a free function in others), and it just so happens to make 
function chaining cleaner in some cases. But there's no reason to try and turn 
all function calls in UFCS calls, and I think that perform and tap are taking 
it too far.

- Jonathan M Davis


More information about the Digitalmars-d mailing list