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