Thread overview
std.algorithm.map with side-effects
Dec 05, 2014
bearophile
Dec 06, 2014
bearophile
Dec 06, 2014
evenex
December 05, 2014
Here's a little experiment I was trying out earlier today in order to try and convert foreach-style code to using UFCS of ranges:

//////////////////////////////////////////////
import std.algorithm, std.range, std.stdio;

void main()
{
    size_t s = 0;

    void essify(size_t n)
    {
        writeln("n = ", n);
        ++s;
    }

    auto filteredRange = iota(0, 10).filter!(a => (a % 2));

    filteredRange.map!(a => essify(a));

    writeln(s);

    foreach (n; filteredRange)
    {
        essify(n);
    }

    writeln(s);
}
//////////////////////////////////////////////

I'd assumed the two uses of filteredRange would produce equivalent results, but in fact the transformation using map does nothing -- the writeln statement inside the essify() function never gets triggered, suggesting the function body is never executed.

Can anyone advise why, and whether there's a nice range iteration option to ensure that this function gets called using each element of the filteredRange?
December 05, 2014
Joseph Rushton Wakeling:

> Can anyone advise why,

map is lazy, like most other ranges.


> and whether there's a nice range iteration option to ensure that this function gets called using each element of the filteredRange?

Lazy higher order functions like map/filter should be used only with pure functions. There are bugs/troubles in using them on impure code.

There was a proposal for a "each" function to terminate a range chain with something effectful, but I think it has gone nowhere. This means you have to use a foreach on a range.

Bye,
bearophile
December 06, 2014
On 06/12/14 00:58, bearophile via Digitalmars-d-learn wrote:
> Joseph Rushton Wakeling:
>
>> Can anyone advise why,
>
> map is lazy, like most other ranges.

Ah, I see.  That function would only be called on consumption of the results of the map.

> Lazy higher order functions like map/filter should be used only with pure
> functions. There are bugs/troubles in using them on impure code.

Yes, I did wonder about that.  I'll post up the actual code tomorrow -- I was having some fun playing with one of the metrics in my Dgraph library and trying to see to what extent I could simplify it (reading-wise) with a range-based approach.

> There was a proposal for a "each" function to terminate a range chain with
> something effectful, but I think it has gone nowhere. This means you have to use
> a foreach on a range.

Yes, I remember you requesting that.  Were there ever any PRs, or was it just spec?
December 06, 2014
map won't actually compute anything until you start asking for
individual elements with front, back, or opIndex.

Personally I like to use something like

  ref transform (alias action, R, T...)(ref R range, T addl_args)
    {
      range = action (range, addl_args);
      return range;
    }

to do mutation in the middle of UFCS chains. Its more flexible
and more obvious than an impure map.

    range.callchain.array.transform!(x => x.map!whatever).filter.etc
December 06, 2014
Joseph Rushton Wakeling:

> Yes, I remember you requesting that.  Were there ever any PRs, or was it just spec?

I think two different persons implemented something like "each". A similar function is used very commonly in F#, where it's named "iter":
http://msdn.microsoft.com/en-us/library/ee340469.aspx

Bye,
bearophile