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