November 09, 2012
On 11/8/2012 8:18 PM, Adam D. Ruppe wrote:
> On Friday, 9 November 2012 at 03:45:11 UTC, Nick Sabalausky wrote:
>> the *one* thing I hate about D ranges is that they don't force you to
>> explicitly say "Yes, I *intend* this to be an InputRange" (what are we, Go
>> users?).
>
> Just a note, of course it still wouldn't *force*, but maybe it'd be a good habit
> to start writing this:
>
> struct myrange {...}
> static assert(isInputRange!myrange);
>
> It'd be a simple way to get a check at the point of declaration and to document
> your intent.
>
>
> Interestingly, we could also do this if the attributes could run through a
> template:
>
> [check!isInputRange] struct myrange{}
>
> @attribute template check(something, Decl) {
>     static assert(something!Decl);
>     alias check = Decl;
> }


Many algorithms (at least the ones in Phobos do) already do a check to ensure the inputs are the correct kind of range. I don't think you'll get very far trying to use a range that isn't a range.

Of course, you can always still have bugs in your range implementation.
November 09, 2012
On Thursday, November 08, 2012 21:10:55 Walter Bright wrote:
> Many algorithms (at least the ones in Phobos do) already do a check to ensure the inputs are the correct kind of range. I don't think you'll get very far trying to use a range that isn't a range.
> 
> Of course, you can always still have bugs in your range implementation.

Given that a range requires a very specific set of functions, I find it highly unlikely that anything which isn't a range will qualify as one. It's far more likely that you screw up and a range isn't the right kind of range because one of the functions wasn't quite right.

There is some danger in a type being incorrectly used with a function when that function requires and tests for only one function, or maybe when it requires two functions. But I would expect that as more is required by a template constraint, it very quickly becomes the case that there's no way that any type would ever pass it with similarly named functions that didn't do the same thing as what they were expected to do. It's just too unlikely that the exact same set of function names would be used for different things, especially as that list grows. And given that ranges are a core part of D's standard library, I don't think that there's much excuse for having a type that has the range functions but isn't supposed to be a range. So, I really don't see this as a problem.

- Jonathan M Davis
November 09, 2012
On Thu, Nov 08, 2012 at 11:51:29PM -0500, Nick Sabalausky wrote:
> On Thu, 8 Nov 2012 20:17:24 -0800
> "H. S. Teoh" <hsteoh@quickfur.ath.cx> wrote:
> > 
> > Actually, I just thought of a solution to the whole duck-typing range thing:
> > 
> > 	struct MyRange {
> > 		// Self-documenting: this struct is intended to be a
> > 		// range.
> > 		static assert(isInputRange!MyRange,
> > 			"Dude, your struct isn't a range!"); //
> > asserts
> > 
> > 
> On Fri, 09 Nov 2012 05:18:59 +0100
> "Adam D. Ruppe" <destructionator@gmail.com> wrote:
> > 
> > Just a note, of course it still wouldn't *force*, but maybe it'd be a good habit to start writing this:
> > 
> > struct myrange {...}
> > static assert(isInputRange!myrange);
> > 
> 
> Those are only half-solutions as they only prevent false-negatives, not false-positives. Plus, there's nothing to prevent people from forgetting to do it in the first place.

IOW, you want the user-defined type to declare that it's an input range, and not just some random struct that happens to have input range like functions?

What about modifying isInputRange to be something like this:

	template isInputRange(R) {
		static if (R.implementsInputRange &&
			/* ... check for input range properties here */)
		{
			enum isInputRange = true;
		} else {
			enum isInputRange = false;
		}
	}

Then all input ranges will have to explicitly declare they are an input range thus:

	struct MyInpRange {
		// This asserts that we're trying to be an input range
		enum implementsInputRange = true;

		// ... define .empty, .front, .popFront here
	}

Any prospective input range that doesn't define implementsInputRange will be rejected by all input range functions. (Of course, that's just a temporary name, you can probably think of a better one.)

You can also make it a mixin, or something like that, if you want to avoid the tedium of defining an enum to be true every single time.


T

-- 
Long, long ago, the ancient Chinese invented a device that lets them see through walls. It was called the "window".
November 09, 2012
On 11/8/2012 9:24 PM, Jonathan M Davis wrote:
> So, I really don't see this as a problem.

Neither do I.

BTW, there's no compiler magic in the world that will statically guarantee you have a non-buggy implementation of a range.

November 09, 2012
On Thursday, November 08, 2012 21:49:52 Walter Bright wrote:
> BTW, there's no compiler magic in the world that will statically guarantee you have a non-buggy implementation of a range.

That's what unit tests are for. :)

- Jonathan M Davis
November 09, 2012
On 11/9/12, H. S. Teoh <hsteoh@quickfur.ath.cx> wrote:
> Then all input ranges will have to explicitly declare they are an input range thus:

Or:

Change definition of isInputRange to:

---
template isInputRange(T)
{
    enum bool isInputRange = (__attributes(T, RequiresInputRangeCheck)
                              && __attributes(T, IsInputRangeAttr))
        || is(typeof(
        (inout int _dummy=0)
        {
            R r = void;       // can define a range object
            if (r.empty) {}   // can test for empty
            r.popFront();     // can invoke popFront()
            auto h = r.front; // can get the front of the range
        }));
}
---

and in your user module:

---
module foo;
@RequiresInputRangeCheck:  // apply attribute to all declarations in this module

@IsInputRangeAttr struct MyRange { /* front/popFront/empty */ } struct NotARange { /* front/popFront/empty defined but not designed to be a range */ }
---

---
static assert(isInputRange!MyRange);
static assert(!(isInputRange!NotARange));
---

That way you keep compatibility with existing ranges and introduce extra safety check for new types which want to be checked.
November 09, 2012
On Thu, Nov 08, 2012 at 09:37:00PM -0800, H. S. Teoh wrote:
> On Thu, Nov 08, 2012 at 11:51:29PM -0500, Nick Sabalausky wrote:
[...]
> > Those are only half-solutions as they only prevent false-negatives, not false-positives. Plus, there's nothing to prevent people from forgetting to do it in the first place.
> 
> IOW, you want the user-defined type to declare that it's an input range, and not just some random struct that happens to have input range like functions?
> 
> What about modifying isInputRange to be something like this:
> 
> 	template isInputRange(R) {
> 		static if (R.implementsInputRange &&
> 			/* ... check for input range properties here */)
> 		{
> 			enum isInputRange = true;
> 		} else {
> 			enum isInputRange = false;
> 		}
> 	}
> 
> Then all input ranges will have to explicitly declare they are an input range thus:
> 
> 	struct MyInpRange {
> 		// This asserts that we're trying to be an input range
> 		enum implementsInputRange = true;
> 
> 		// ... define .empty, .front, .popFront here
> 	}
> 
> Any prospective input range that doesn't define implementsInputRange will be rejected by all input range functions. (Of course, that's just a temporary name, you can probably think of a better one.)
> 
> You can also make it a mixin, or something like that, if you want to avoid the tedium of defining an enum to be true every single time.
[...]

Here's a slight refinement:

	// Note: untested code
	mixin template imAnInputRange() {
		static assert(isInputRange!(typeof(this)));
		enum implementsInputRange = true;
	}

	struct MyInpRange {
		// This should croak loudly if this struct isn't a valid
		// input range. Omitting this line makes range functions
		// reject it too (provided we modify isInputRange as
		// described above).
		mixin imAnInputRange;

		// implement range functions here
	}


T

-- 
It only takes one twig to burn down a forest.
November 09, 2012
On Thu, Nov 08, 2012 at 10:03:03PM -0800, Jonathan M Davis wrote:
> On Thursday, November 08, 2012 21:49:52 Walter Bright wrote:
> > BTW, there's no compiler magic in the world that will statically guarantee you have a non-buggy implementation of a range.

Yeah, that's one major missing feature from D/Phobos/etc.: a mixin template called EliminateBugs that will fix all your program's bugs for you. I think that should be the next top priority on D's to-do list! ;-)


> That's what unit tests are for. :)
[...]

Well, unittests are a runtime check, and they don't *guarantee* anything. (One could, in theory, write a pathological pseudo-range that passes basic unittests but fail to behave like a range in some obscure corner case. Transient ranges would fall under that category, should we decide not to admit them as valid ranges. :-))

But of course that's just splitting hairs.


T

-- 
Amateurs built the Ark; professionals built the Titanic.
November 09, 2012
On 11/8/2012 10:20 PM, H. S. Teoh wrote:
> But of course that's just splitting hairs.

"Let's not go splittin' hares!"
   -- Bugs Bunny

November 09, 2012
On Thu, 8 Nov 2012 21:37:00 -0800
"H. S. Teoh" <hsteoh@quickfur.ath.cx> wrote:
> 
> IOW, you want the user-defined type to declare that it's an input range, and not just some random struct that happens to have input range like functions?
> 

Yes.

> What about modifying isInputRange to be something like this:
> 
> 	template isInputRange(R) {
> 		static if (R.implementsInputRange &&
> 			/* ... check for input range properties here
> */) {
> 			enum isInputRange = true;
> 		} else {
> 			enum isInputRange = false;
> 		}
> 	}
> 
> Then all input ranges will have to explicitly declare they are an input range thus:
> 
> 	struct MyInpRange {
> 		// This asserts that we're trying to be an input range
> 		enum implementsInputRange = true;
> 
> 		// ... define .empty, .front, .popFront here
> 	}
> 

Close. At that point you need two steps:

struct MyInpRange {
    // Declare this as an input range
    enum implementsInputRange = true;

    // Enforce this really *IS* as an input range
    static assert(isInputRange!MyRange,
        "Dude, your struct isn't a range!"); // asserts

    // ... define .empty, .front, .popFront here
}

My suggestion was to take basically that, and then wrap up the "Declare and Enfore" in one simple step:

struct MyInpRange {

    // Generate & mixin *both* the "enum" and the "static assert"
    mixin(implements!InputRange);

    // ... define .empty, .front, .popFront here
}

/Dreaming:

Of course, it'd be even nicer still to have all this wrapped up in
some language sugar (D3? ;) ) and just do something like:

struct interface InputRange {
    // ... *declare* .empty, .front, .popFront here
}

struct interface ForwardRange : InputRange {
    // ... *declare* .save here
}

struct MyForwardRange : ForwardRange {
    // ... define .empty, .front, .popFront, .save here
    // Actually validated by the compiler
}

Which would then amount to what we're doing by hand up above. So kinda like Go, except not error-prone and ducky and all shitty.