www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.bugs - [Issue 16326] New: filter is not lazy enough & has weird save behavior

https://issues.dlang.org/show_bug.cgi?id=16326

          Issue ID: 16326
           Summary: filter is not lazy enough & has weird save behavior
           Product: D
           Version: D2
          Hardware: All
               URL: http://dlang.org/
                OS: All
            Status: NEW
          Severity: normal
          Priority: P3
         Component: phobos
          Assignee: nobody puremagic.com
          Reporter: eyal weka.io

std.algorithm.filter's this() constructor advances the underlying range until
the predicate is fulfilled. This happens *BEFORE* its next item is demanded
(i.e: Not lazily).

This has a number of unfortunate consequences:

Firstly, it makes 'save' which uses the constructor to copy the range sensitive
to mutations in the underlying range:

void sensitiveToMutations() {
    {
        auto f = [1,2,3].filter!(x => x%2==0);
        auto g = f.save;
        f.front = 5;
        assert(g.front == 5); // works
    }
    {
        auto f = [1,2,3].filter!(x => x%2==0);
        f.front = 5;
        auto g = f.save;
        assert(g.front == 5); // fails!
    }
}

The above is reduced from actual confusing behavior we've experienced.

This extra eagerness can easily cause infinite loops:

void loopsForever() {
    import std.range : repeat;

    // This loops forever:
    auto f = repeat(1).filter!(x=>x%2 == 0);
}

And lastly, if the predicate is expensive or has undesirable side effects that
we wish to keep to a minimum, filter is wasteful:

void redundantPredicateCalls() {
    import std.stdio : writeln;

    auto veryExpensivePredicate(int x) { writeln("OUCH"); return true; }

    // OUCH number 1 (already redundant)
    auto f = [1,2,3].filter!veryExpensivePredicate;

    // OUCH number 2 (Redundant!)
    auto g = f.save;
}

--
Jul 27 2016