is std.algorithm.joiner lazy?

Jonathan M Davis via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Thu Apr 7 19:49:01 PDT 2016


On Friday, April 08, 2016 02:01:07 Puming via Digitalmars-d-learn wrote:
> So what you mean is to read the front in constructor, and read
> further parts in the popFront()? that way multiple access to the
> front won't hurt anything. I think it might work, I'll change my
> code.
>
> So the guideline is: when accessing front is costly, don't use
> map, use a customized range struct instead. right?

In general, when you're dealing with a non-random access range, it's best
for popFront to do the work of setting up front and then have front return
the same object every time. If front is doing the work, then if it gets
called multiple times, that work is being repeated every time it gets
called. map is a funny case, because it can be a random-access range (if the
underlying range it's wrapping is a random-access range). So, fundamentally,
it doesn't work in map to do the work in popFront. It pretty much has to be
done in front. So, doing stuff like range.map!(a => to!string(a))() is
problematic in that a new allocation is going to occur every time that front
is called - or when any element is accessed via opIndex. It works so long as
the element is equal every time, and calling front multiple times does not
affect the rest of the range, but it can be costly. In theory, cache should
solve that case (and it would result in a range that wasn't random access,
so opIndex wouldn't be called on it), but obviously, you're running into
problems with it.

In any case, in general, when doing something like reading from a file with
a range, it works best to do the work in popFront to avoid issues with
multiple calls to front, and the constructor needs to do that work as well
(be it by calling popFront or not), because front needs to be valid as soon
as the range has been created, and it's not empty. So, you end up with
something like

struct MyRange
{
public:
    @property T front() { return _value; }
    @property bool empty() { ... }
    void popFront()
    {
        _value = readNextValueFromFile();
    }

private:

    this(Something s)
    {
        ...
        popFront();
    }

    T _value;
}

It also encapsulates things better than having a function whose only purpose
is to be used in map, though there are obviously cases where writing a
function just to use in map would make sense.

In general, I would only use map for cases where I'm converting something to
something else and not for functions that do arbitrary work. A function for
map that cannot be pure is a danger sign IMHO. Certainly, if you're going to
follow how ranges are expected to work, whatever function you give map needs
to return equal values every time front is called between calls to popFront,
and multiple calls to front cannot affect the rest of the range.  And what
you did with map, doesn't follow those guidelines, though it probably would
if cache worked, and you always fed it into cache.  Still, for something
like this, I'd just create my own range and be done with it. You often need
to anyway in order to manage extra state. And it tends to be more idiomatic,
though I suppose that that's somewhat subjective.

- Jonathan M Davis



More information about the Digitalmars-d-learn mailing list