A Small Contribution to Phobos

monarch_dodra monarchdodra at gmail.com
Sun Jun 2 11:43:42 PDT 2013


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...?



More information about the Digitalmars-d mailing list