On Sunday, 2 June 2013 at 18:43:44 UTC, monarch_dodra wrote:
On Sunday, 2 June 2013 at 16:55:23 UTC, Andrei Alexandrescu
wrote:
On 6/2/13 11:41 AM, monarch_dodra wrote:
On Sunday, 2 June 2013 at 13:07:18 UTC, Andrei Alexandrescu
wrote:
[1, 2, 3, 4].map!(n => n.writeln).reduce;
Andrei
One of the problems with using "map" for something such as
this, is that
the resulting object is not a range, since "front" now
returns void, and
a range *must* return a value. So that code will never
compile (since
reduce will ask for at least input range). Heck, I think we
should make
it so that map refuses to compile with an operator that
returns void. It
doesn't make much sense as-is.
Hm, interesting. I'm destroyed.
Usage has to be something like:
map!((n) {n.writeln; return n;})
which is quite clunky. The idea of a "tee" range, that takes
n, runs an
operation on it, and then returns said n as is becomes really
very
useful (and more idiomatic). [1, 2, 3, 4].tee!(n =>
n.writeln). There!
perfect :)
I've dabbled in implementing such a function, but there are
conceptual
problems: If the user calls "front" twice in a row, then
should "fun" be
called twice? If user popsFront without calling front, should
"fun" be
called at all?
Should it keep track of calls, to guarantee 1, and only 1,
call on each
element?
I'm not sure there is a correct answer to that, which is one
of the
reasons I haven't actually submitted anything.
I think there is one answer that arguably narrows the design
space appropriately: just like the Unix utility, tee should
provide a hook that creates an exact replica of the (portion
of the) range being iterated. So calling front several times
is nicely out of the picture. The remaining tactical options
are:
1. evaluate .front for the parent range once in its
constructor and then every time right after forwarding
popFront() to the parent range. This is a bit "eager" because
the constructor evaluates .front even if the client never does.
2. evaluate .front for the parent range just before forwarding
popFront() to parent. This will call front even though the
client doesn't (which I think is fine).
3. keep a bool that is set by constructor and popFront() and
reset by front(). The bool makes sure front() is called if and
only if the client calls it.
I started writing the options mechanically without thinking of
the implications. Now that I'm done, I think 2 is by far the
best.
I think I just had a good idea. First, we introduce "cached":
cached will take the result of front, but only evaluate it
once. This is a good idea in and out of itself, and should take
the place of ".array()" in UFCS chains. It can store the result
of an operation, but keeps the lazy iteration semantic. That's
a win for functional programming right there.
It would be most convenient right after an expansive call, such
as after a map or whatnot.
The semantic of "cached" would be:
"eagerly calls front once, always once, and exactly once, and
stores the result. Calling front on cached returns said result.
calling popFront repeats operation".
From there, "tee", is nothing more than "calls funs on the
front element every time front is called, then returns front".
From there, users can user either of:
MyRange.tee!foo(): This calls foo on every front element, and
several times is front gets called several times.
MyRange.tee!foo().cached(): This calls foo on every front
element, but only once, and guaranteed at least once, if it
gets iterated.
--------
I don't think "argument-less reduce" should do what you
describe, as it
would be a bit confusing what the function does. 1-names;
1-operation,
IMO. Users might accidentally think they are getting an
additive
reduction :(
Good point.
I think a function called "walk", in line with "walkLength",
would be
much more appropriate, and make more sense to boot!
But we run into the same problem... Should "walk" call front
between
each element? Both answers are correct, IMO.
That's why I'm thinking: the moment .front gets evaluated, we
get into the realm of reduce.
Combined with my "cached" proposal, the problem is solved I
think: "walk" does not call front, it merely pops. But, if
combined with cached, then cache *will*, call front. Once and
exactly once.
This will call foo on all elements of my range (once exactly
once):
MyRange.tee!foo().cached().walk();
--------
Unless I'm missing something, it looks like a sweet spot
between functionality, modularity, and even efficiency...?
I like the idea of "cached" and it's certainly useful if you need
to iterate a range multiple times or something like that, but I
also think that 90% of the time the user is just going to want to
do something simple such as printing every element, and I think
the syntax "tee!(x => writeln(x)).cached.walk();" is both
unnecessarily long and less efficient than simply:
consume!(x => writeln(x)); // Template parameter is optional
"consume" would always call front once per element even if no
function is specified.