std.algorithm.joiner unexpected behavior

H. S. Teoh via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Thu Aug 31 17:09:16 PDT 2017


On Thu, Aug 31, 2017 at 05:37:20PM -0600, Jonathan M Davis via Digitalmars-d-learn wrote:
> On Thursday, August 31, 2017 14:09:55 H. S. Teoh via Digitalmars-d-learn 
[...]
> I know. We've had this argument before.

I know. Let's not rehash that. :-)


[...]
> Even copying ranges in generic code does not have defined behavior,
> because different types have different semantics even if they follow
> the range API and pass isInputRange, isForwardRange, etc.

This is actually one of the prime reasons for my stance that we ought to
always use .save instead of assigning .front to a local variable.

Consider the case where .front returns a subrange.  As you state above,
copying this subrange does not have defined behaviour. One reason is the
difference in semantics between reference types and value types: the
assignment operator `=` means different things for each kind of type.
`x=y;` for a value type makes a new copy of the value in x, but `x=y;`
for a reference type only copies the reference, not the value.  So how
do you ensure that after assigning .front to a local variable, it will
continue to be valid after .popFront is called on the outer range? You
can't.  If you simply assign it, there's no guarantee it isn't just
copying a reference to a buffer reused by .popFront.  But if you try to
copy it, the result is not defined, as you said.

Worse yet, the user can overload opAssign() to do something with
side-effects. So this code:

	auto e = r.front;
	r.popFront();
	userCallback(e);

may potentially have a different result from:

	userCallback(r.front);
	r.popFront();

The only safe approach is to make as few assumptions as possible, i.e.,
don't assume that `=` will produce the right result, so avoid saving
anything in local variables completely and always use .save instead if
you need to refer to a previous value of .front after calling .popFront.

Yes this greatly complicates generic code, and I wouldn't impose it on
user code.  But one would expect that Phobos, at the very least, ought
to be of a high enough standard to be able to handle such things
correctly.


Taking a step back from these nitpicky details, though: this seems to be
symptomic of the underlying difficulty of nailing exact range semantics
in an imperative language.  In a pure functional language without
mutation, you wouldn't have such issues, so there you could compose
arbitrarily complex ranges of arbitrarily complex behaviours with
impunity and not have to worry that something might break if one of the
subranges was transient.  We can't kill mutation in D, though, so
unfortunately we have to live with these complications.


T

-- 
Life begins when you can spend your spare time programming instead of watching television. -- Cal Keegan


More information about the Digitalmars-d-learn mailing list