Jump to page: 1 2 3
Thread overview
First class lazy Interval
Feb 27, 2009
bearophile
Feb 27, 2009
bearophile
Feb 27, 2009
Michel Fortin
Feb 27, 2009
bearophile
Feb 27, 2009
Lutger
Feb 27, 2009
Denis Koroskin
Feb 27, 2009
Don
Feb 27, 2009
Daniel Keep
Feb 27, 2009
Daniel Keep
Feb 27, 2009
bearophile
Feb 28, 2009
downs
Feb 28, 2009
Jesse Phillips
Feb 28, 2009
Michel Fortin
Feb 28, 2009
Daniel Keep
Feb 28, 2009
Michel Fortin
Feb 28, 2009
Bill Baxter
Feb 28, 2009
Rainer Deyke
Mar 02, 2009
Joel C. Salomon
Mar 03, 2009
Don
Mar 03, 2009
Denis Koroskin
Feb 28, 2009
Michel Fortin
Feb 28, 2009
Lutger
Feb 27, 2009
Don
Feb 27, 2009
Sean Reque
February 27, 2009
D2 supports the interval syntax in the foreach:
foreach (i; 1..1000) {...}

Such intervals are useful in a very large number of situations. So, with the new Range support, it may be useful to allow the interval syntax to be used in other contexts as well.
So x..y may become a first-class lazy interval from x to y-1, that can be passed to functions too, etc, and not just used into foreach (the compiler can recognize it, and often optimize it away in many situations, replacing it with a normal for() loop).

Optional possibilities:

1) x..y..s
where s is a step/stride, so you can define odd numbers, etc.
1..10..3 ==> 1 4 7
10..1..-2 ==> 10 8 6 4 2
1..5..1 ==> 1 2 3 4
I don't like this syntax much, but I think it's acceptable.

2) Such intervals may enjoy a fast opIn_r() method, for quick membership test:
if (a in 0..10) {...}
if (c in 'c'..'z'+1) {...}
(Notice the +1 because all intervals are open on the right).
For the compiler that's syntax sugar of (a>0 && a<10).
For the user I think it's easy to remember such syntax.

Originally I have thought about the syntaxes [x..y] and [x..y..s] as eager, that is arrays of the (integer?) numbers of the interval. But they are just sugar for the (future?) function of std.algorithm that produces an array from a lazy iterable, so it may not be useful enough.

Bye,
bearophile
February 27, 2009
bearophile:
> For the compiler that's syntax sugar of (a>0 && a<10).

I meant:
(a >= 0 && a < 10)
February 27, 2009
On 2009-02-27 04:43:46 -0500, bearophile <bearophileHUGS@lycos.com> said:

> D2 supports the interval syntax in the foreach:
> foreach (i; 1..1000) {...}
> 
> Such intervals are useful in a very large number of situations. So, with the new Range support, it may be useful to allow the interval syntax to be used in other contexts as well.
> So x..y may become a first-class lazy interval from x to y-1, that can be passed to functions too, etc, and not just used into foreach (the compiler can recognize it, and often optimize it away in many situations, replacing it with a normal for() loop).

I agree that having first-class intervals in the language would make it better, especially when you want to pass intervals as function arguments.

In the D/Objective-C bridge, I've defined the NSRange struct (a Cocoa type representing an integer interval) so it can be created by typing NSRange[start..end] in adition to the traditional NSMakeRange(start, length). It's better than nothing, but even better would be the ability to omit NSRange[] entirely.


> 1) x..y..s
> where s is a step/stride, so you can define odd numbers, etc.
> 1..10..3 ==> 1 4 7
> 10..1..-2 ==> 10 8 6 4 2
> 1..5..1 ==> 1 2 3 4
> I don't like this syntax much, but I think it's acceptable.

May I propose that strides not be part of the interval syntax. Keep the interval simple (x..y) then define operators to transform them. Perhaps the modulus operator could create a steped interval (x..y % s), although this operator doesn't seem to fit exactly right. Perhaps just step(x..y, s) would be enough. It'd return a special struct for iterating the interval in bigger steps.

As for strides, why not just (a..b ~ c..d) for joining two ranges and ~(x..y) for everything outside x..y ?


> 2) Such intervals may enjoy a fast opIn_r() method, for quick membership test:
> if (a in 0..10) {...}
> if (c in 'c'..'z'+1) {...}

That'd be great if you could use them in the switch statement too:

	switch (a)
	{
		case 0..10: break;
	}

The only downside is that it may confuse people who are accustomed to the GCC extension with define an inclusive interval this way:

	switch (a)
	{
		case 0...10: break;
	}

Perhaps we could make 3 dots mean an inclusive interval (including the second value in the interval), and 2 dots an exclusive one (excluding the second value).

-- 
Michel Fortin
michel.fortin@michelf.com
http://michelf.com/

February 27, 2009
Michel Fortin:
> In the D/Objective-C bridge, I've defined the NSRange struct (a Cocoa type representing an integer interval) so it can be created by typing NSRange[start..end] in adition to the traditional NSMakeRange(start, length). It's better than nothing, but even better would be the ability to omit NSRange[] entirely.

In my dlibs I have xrange/range, but I am also looking for something that the compiler can recognize and optimize away better.


> As for strides, why not just (a..b ~ c..d) for joining two ranges and ~(x..y) for everything outside x..y ?

Weeks ago I have strongly suggested Alex to add support for ~ to join any kind of lazy/eager iterable to any other lazy/eager iterable (if the types of the items they yield are equal or castable). I have defined xchain and the Chainable mixin in dlibs for this very useful purpose.


> The only downside is that it may confuse people who are accustomed to the GCC extension with define an inclusive interval this way:

Too bad for them.


> Perhaps we could make 3 dots mean an inclusive interval (including the second value in the interval), and 2 dots an exclusive one (excluding the second value).

Ruby follows your idea, but for the eye it's easy to miss the extra dot, so I don't like this much (but I think an optional stride syntax may be useful, especially if such stride can be negative too. You can also perform a little optimization if the stride is known at compile time, as it often happens).

Bye,
bearophile
February 27, 2009
bearophile wrote:

> Michel Fortin:
...
>> Perhaps we could make 3 dots mean an inclusive interval (including the second value in the interval), and 2 dots an exclusive one (excluding the second value).
> 
> Ruby follows your idea, but for the eye it's easy to miss the extra dot,
so I don't like this much (but I think an optional stride syntax may be useful, especially if such stride can be negative too. You can also perform a little optimization if the stride is known at compile time, as it often happens).
> 
> Bye,
> bearophile

Except in Ruby the meaning is reversed (.. is inclusive and ... is exclusive).

About the stride, in ruby this is done as (0..10).step(2), which is very readable imho. With extension methods if these ever get in D, will the same syntax be possible with std.range?

I do like the stride in std.range (and step in ruby) because it's straightforward and readable:

stride( a, 2 )

could become: stride( 0..10, 2 ) or: (0..10).stride(2)



February 27, 2009
Michel Fortin wrote:
> On 2009-02-27 04:43:46 -0500, bearophile <bearophileHUGS@lycos.com> said:
> 
>> D2 supports the interval syntax in the foreach:
>> foreach (i; 1..1000) {...}
>>
>> Such intervals are useful in a very large number of situations. So, with the new Range support, it may be useful to allow the interval syntax to be used in other contexts as well.
>> So x..y may become a first-class lazy interval from x to y-1, that can be passed to functions too, etc, and not just used into foreach (the compiler can recognize it, and often optimize it away in many situations, replacing it with a normal for() loop).
> 
> I agree that having first-class intervals in the language would make it better, especially when you want to pass intervals as function arguments.

I'm having trouble understanding what's wrong with the good old data types and functions.

Andrei
February 27, 2009
bearophile wrote:
> D2 supports the interval syntax in the foreach:
> foreach (i; 1..1000) {...}
> 
> Such intervals are useful in a very large number of situations. So, with the new Range support, it may be useful to allow the interval syntax to be used in other contexts as well.
> So x..y may become a first-class lazy interval from x to y-1, that can be passed to functions too, etc, and not just used into foreach (the compiler can recognize it, and often optimize it away in many situations, replacing it with a normal for() loop).

How do you specify a uint range that includes uint.max?
(This is a general problem with "[)" ranges).


> Optional possibilities:
> 
> 1) x..y..s
> where s is a step/stride, so you can define odd numbers, etc.
> 1..10..3 ==> 1 4 7
> 10..1..-2 ==> 10 8 6 4 2
> 1..5..1 ==> 1 2 3 4
> I don't like this syntax much, but I think it's acceptable.

This is a perfect example of what Andrei recently posted: proposal of a language change for a pathetically limited special case. Your stride is not powerful enough to be worthwhile.
February 27, 2009
On Fri, 27 Feb 2009 16:44:31 +0300, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:

> Michel Fortin wrote:
>> On 2009-02-27 04:43:46 -0500, bearophile <bearophileHUGS@lycos.com> said:
>>
>>> D2 supports the interval syntax in the foreach:
>>> foreach (i; 1..1000) {...}
>>>
>>> Such intervals are useful in a very large number of situations. So, with the new Range support, it may be useful to allow the interval syntax to be used in other contexts as well.
>>> So x..y may become a first-class lazy interval from x to y-1, that can be passed to functions too, etc, and not just used into foreach (the compiler can recognize it, and often optimize it away in many situations, replacing it with a normal for() loop).
>>  I agree that having first-class intervals in the language would make it better, especially when you want to pass intervals as function arguments.
>
> I'm having trouble understanding what's wrong with the good old data types and functions.
>
> Andrei

The syntax. One may want to reuse 0..100 syntax to generate random number:
auto x = random(0..100); // gimme a random value in [0, 100)

or check if a value belongs to an interval:

T opIndex(size_t index)
{
   assert(index in 0.._size);
   // ...
}

February 27, 2009
Denis Koroskin wrote:
> On Fri, 27 Feb 2009 16:44:31 +0300, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:
> 
>> Michel Fortin wrote:
>>> On 2009-02-27 04:43:46 -0500, bearophile <bearophileHUGS@lycos.com> said:
>>>
>>>> D2 supports the interval syntax in the foreach:
>>>> foreach (i; 1..1000) {...}
>>>>
>>>> Such intervals are useful in a very large number of situations. So, with the new Range support, it may be useful to allow the interval syntax to be used in other contexts as well.
>>>> So x..y may become a first-class lazy interval from x to y-1, that can be passed to functions too, etc, and not just used into foreach (the compiler can recognize it, and often optimize it away in many situations, replacing it with a normal for() loop).
>>>  I agree that having first-class intervals in the language would make it better, especially when you want to pass intervals as function arguments.
>>
>> I'm having trouble understanding what's wrong with the good old data types and functions.
>>
>> Andrei
> 
> The syntax. One may want to reuse 0..100 syntax to generate random number:
> auto x = random(0..100); // gimme a random value in [0, 100)
> 
> or check if a value belongs to an interval:
> 
> T opIndex(size_t index)
> {
>    assert(index in 0.._size);
>    // ...
> }

Particularly, multi-dimensional slices. We have opSlice as a hacky special case of opIndex. So desirable syntax like

auto y = x[2..$, 4, 3..8];

is impossible. You can only do something like:

auto y = x[Range(2,$), 4, Range(3,8)];
or perhaps
auto y = x[Range[2..$], 4, Range[3..8]];
February 27, 2009
Denis Koroskin wrote:
> On Fri, 27 Feb 2009 16:44:31 +0300, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:
>> I'm having trouble understanding what's wrong with the good old data types and functions.
>>
>> Andrei
> 
> The syntax. One may want to reuse 0..100 syntax to generate random number:
> auto x = random(0..100); // gimme a random value in [0, 100)
> 
> or check if a value belongs to an interval:
> 
> T opIndex(size_t index)
> {
>    assert(index in 0.._size);
>    // ...
> }

I don't think this should be in the language... it just feels like there isn't enough use for it.  Especially not when you can implement it as a library.

As proof, I've attached an implementation of an integral interval. Here's a sample of usage (the program prints this if you run it):

> inter[0, 10] = inter[0, 10]
> inter[0..10] = inter[0, 9]
> inter(0, 10) = inter[1, 9]

[a,b] is inclusive, (a,b) is exclusive and [a..b] is lower inclusive,
upper exclusive (to match slicing syntax).

> inter[0..10] % 2 = inter[0, 8] / 2
> inter[1..10] % 2 = inter[2, 8] / 2
> inter[1..10] / 2 = inter[1, 9] / 2
> 6 in inter[0..10] % 2 = true
> 7 in inter[0..10] % 2 = false

I % a produces an interval of all values n in I which satisfy:
 n % a == 0.

I / a produces an interval containing every a'th element of I (including
the first).

> inter[0, 30] / 2 = inter[0, 30] / 2
> (inter[0, 30] / 2).length = 16
> (inter[0, 30] / 2) / 3 = inter[0, 30] / 6
> ((inter[0, 30] / 2) / 3).length = 6
> ((inter[0, 30] / 2) / 3).toArray = [0 6 12 18 24 30]

It should support indexing, slicing and length-ing.  It should also support ranges (can't test it, of course :P).


  -- Daniel




« First   ‹ Prev
1 2 3