Transience of .front in input vs. forward ranges

H. S. Teoh hsteoh at quickfur.ath.cx
Tue Oct 30 17:56:32 PDT 2012


On Wed, Oct 31, 2012 at 12:00:50AM +0100, deadalnix wrote:
[...]
> It appears to me that invalidating .front is done for performances
> reasons most of the time. As this is the unsafe face of the coin, I
> strongly think that this shouldn't be the default behavior.
> 
> To me the best solution is to provide a function or a property on
> ranges that provide the unsafe version of the range.
> 
> It is easy to provide a default implementation as UFCS that is a
> NOOP so no code is being broken.
> 
> With that design, it is possible to provide an always safe interface
> while allowing algorithm that can handle non persistent .front to
> run at full speed.

Hmm. I like this idea! This is much better than my proposal. So let's
say we change byLine() to always return a copy of the buffer, so that
.front is never invalidated. Then for algorithms that don't care about
.front being invalidated, they can do something like this:

	auto myAlgo(R)(R inputRange) {
		auto r = inputRange.fastRange;
		while (!r.empty) {
			auto x = r.front;
			// make use of x
			r.popFront();	// this invalidates x
		}
		return result;
	}
	void main() {
		myAlgo(stdin.byLine());
	}

We can use UFCS so that fastRange is always defined:

	// Default implementation
	auto fastRange(R)(R range) {
		return range;
	}

For byLine(), then, we have:

	struct ByLine(...) {
		// safe implementation

		@property auto fastRange() {
			struct UnsafeByLine(...) {
				...
			}
			return UnsafeByLine(this);
		}
	}

I like this. Very clean, and doesn't break backward compatibility, and
allows select algorithms to be optimized for speed without affecting
everything else.


[...]
> If I use your permuting example, it should be done as follow :
> 
> struct InvalidatingAllPermutations(T) {
> 	T[] current, first;
> 	bool done;
> 
> 	this(T[] initialBuf) {
> 		first = initialBuf;
> 		current = first.dup;
> 	}
> 
> 	void popFront() {
> 		nextPermutation(current);
> 		if (equal(current, first))
> 			done = true;
> 	}
> 
> 	@property bool empty() {
> 		return !done;
> 	}
> 
> 	@property T[] front() {
> 		return current;
> 	}
> 
> 	auto save() {
> 		AllPermutations!T copy;
> 		copy.front = this.front;
> 		copy.first = this.first;
> 		copy.done = this.done;
> 		return copy;
> 	}
> }
> 
> struct AllPermutations(T) {
> 	InvalidatingAllPermutations!T innerRange;
> 	alias innerRange this;
> 
> 	T[] current;
> 
> 	this(T[] initialBuf) {
> 		innerRange = InvalidatingAllPermutations!T(initialBuf);
> 		current = innerRange.front.dup;
> 	}
> 
> 	@property T[] front() {
> 		return current;
> 	}
> 
> 	void popFront() {
> 		innerRange.popFront();
> 		current = innerRange.front.dup;
> 	}
> }
> 
> I do think this is the way that conciliate safe as default but still
> allow to be fast when needed, without adding much on most code.

+1. I like this better than my proposal.


T

-- 
Doubtless it is a good thing to have an open mind, but a truly open mind
should be open at both ends, like the food-pipe, with the capacity for
excretion as well as absorption. -- Northrop Frye


More information about the Digitalmars-d mailing list