Range.init does not imply Range.empty==true

H. S. Teoh hsteoh at quickfur.ath.cx
Thu Dec 20 14:35:15 PST 2012


On Thu, Dec 20, 2012 at 11:04:05PM +0100, Jonathan M Davis wrote:
> On Thursday, December 20, 2012 13:08:46 H. S. Teoh wrote:
> > > However, any range which is a struct whose init value _isn't_ empty is
> > > wrong. We should be be able to rely on that at least.
> > 
> > [...]
> > 
> > No we can't. For example:
> > 
> > auto someRangeFunction(RoR)(RoR ror) {
> > struct Helper {
> > RoR _ror;
> > ElementType!R _current;
> > 
> > this(RoR r) {
> > _ror = r;
> > assert(_current.empty);
> > }
> > }
> > return Helper(ror);
> > }
> > 
> > If RoR is an array of class objects, the assert line will segfault
> > because _current.init is null. Conceptually speaking, I agree that a
> > null object is an empty range, but in code, .empty is undefined
> > because you can't invoke a method through a null reference.
> 
> If _current is an array, then it won't segfault. A null array returns true for 
> empty. Only classes have problems with null.

Right.


> But regardless, my point is that Range.init.empty must be true _for
> structs_.  A struct range should be written such that its init value
> is empty. That's not true for classes, because they'll be null.
> 
> Your assertion should pass as long as the type of _current is a
> struct.

The problem with generic code is that you can't assume this. The type
could be literally _anything_ (as long as it conforms to the range API).
Unless you protect it with static ifs or something similar, of course.


> If you want to get an empty range from an existing range, then use
> takeNone.  If it returns the same type (which it tries to do but can't
> always do - e.g.  with non-sliceable ranges which are classes or
> non-sliceable Voldemort types), then you can reassign it to the
> original. If it doesn't, then there's no generic way to get an empty
> range of the same type from that range other than calling popFront
> until it's empty (which is obviously ineffecient in the finite case
> and impossible in the infinite case).
[...]

Which is why I'm leaning towards rewriting algorithms so that they don't
rely on ranges being emptiable. (Like you said, it's easy to have a
range class that's infinite, in which case there is no empty state for
ranges of that type at all -- .empty is an enum set to false.) I find
that the code often becomes cleaner when written in a way that minimizes
extraneous assumptions. It's harder to write, for sure, but I think it's
more rewarding in the end.

Anyway, coming back to takeNone: in many cases takeNone won't return the
exact same type as the original range, so code like this won't work
anymore:

	auto myRangeFunc(R)(R range) {
		struct Wrapper {
			R _src;
			R _helper;

			this(R src) {
				_src = src;
				...

				// Works:
				_helper = _src;
				...

				// Won't work: R != typeof(takeNone!R)
				// in general.
				_helper = takeNone(src);
			}
		}
	}

To allow both _helper=_src and _helper=takeNone(src) to work, one would
have to make _helper a wrapper type that can be assigned both R and
typeof(takeNone!R). Is this possible with the current implementation of
takeNone? I glanced at the code briefly but couldn't tell for sure.


T

-- 
Marketing: the art of convincing people to pay for what they didn't need
before which you can't deliver after.


More information about the Digitalmars-d mailing list