A Small Contribution to Phobos
Diggory
diggsey at googlemail.com
Sun Jun 2 16:58:24 PDT 2013
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.
More information about the Digitalmars-d
mailing list