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