February 27, 2009
Daniel Keep wrote:
> 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):

This is pretty darn cool!

Andrei
February 27, 2009

Andrei Alexandrescu wrote:
> Daniel Keep wrote:
>> 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):
> 
> This is pretty darn cool!
> 
> Andrei

Thanks.

On the off chance anyone wants to actually use it, I hereby release the interval module attached to my previous post into the public domain.

Anyway, it's 2:30 am here, so I'm off to bed.  :P

  -- Daniel
February 27, 2009
I think Ruby is a good example of a good way to implement intervals. Ruby exposes them as a class object, so that you can instantiate them as Range.new(min,max), and a Range object iterable and can easily be converted to an array. It also supports inclusion testing. The only thing it doesn't do is allow one to specify a step in the object itself, at least as of 1.8. (One CAN step over a range with the step function, but this doesn't help for things like inclusion testing). In addition to exposing a class interface that is consistent with the rest of the language, the ruby language itself allows one to define ranges using the .. notation as extra syntactic sugar.

I really like Ruby's combination of defining constructs in the language consistently so that they looko the same as user-defined objects, and then adding syntactic sugar on top to make the language more pleasant.  Regular expressions are another example of how Ruby does this. Ruby allows one to instantiate regular expressions the same way in as in python, as they are implemented as a Regexp class, but Ruby also supports instantiating them using perl's syntax, which underneath the hood is just creating a Regexp object. The advantage of the perl-like syntax is that editors and IDEs can aid a user with syntax highlighting, whereas when creating a regular expression with a string no such help can be given.

Doing things similarly in D, one could implement intervals in a library form in a similar manner as Daniel did, and then add support in the language for the syntax bearophile described. Of course, this would require that D would impose that at least certain things got implemented in a standard library, whereas D currently doesn't do if I understand correctly..
February 27, 2009
Don:

>How do you specify a uint range that includes uint.max?<

The interval I was talking about may generate integers only (it may be possible to generate longs and chars too if you give it long/char extrema, but I am not sure this is a good idea).


>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.<

It's limited on purpose, but such intervals are useful in many situations.

And note such interval is less limited than the current syntax of D2, that allows such interval as foreach argument only.

This post of mine was just an idea, surely most of it can be improved :-)

---------------------

Daniel Keep:

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

I'll probably not be able to remember that. What I'm looking for is something simple that covers the most common cases, not all of them introducing excessive burden to the memory. In designing such things the 80/20 rule is good.

---------------------

Don:

> So desirable syntax like auto y = x[2..$, 4, 3..8]; is impossible.<

I see.

In Haskell you can use the syntax [x..] to denote the half interval of integers from x to infinite.

So in D2 x..$ may be used to denote an interval infinte on the right... or (contextual-wise) one interval extended to the max possible value (max int, or max length of the iterable/data structure?). I am not sure such double meaning is a good thing.

---------------------

Lutger:

>About the stride, in ruby this is done as (0..10).step(2), which is very readable imho.<

It's more readable than
0 .. 10 .. 2
but I think you can get used to 0..10..2 too, because you probably end using such syntax often in programs.

---------------------

Sean Reque:

>but Ruby also supports instantiating them using perl's syntax, which underneath the hood is just creating a Regexp object. The advantage of the perl-like syntax is that editors and IDEs can aid a user with syntax highlighting, whereas when creating a regular expression with a string no such help can be given.<

Languages are designed for different purposes and with different ideas in mind. Syntax-wise Python is simpler, less tricky (*) and more uniform than Ruby. This makes Python less complex to learn, but less able to create custom-purpose sub-languages, etc.

(* = see functions called as foo 5)


>Doing things similarly in D, one could implement intervals in a library form in a similar manner as Daniel did, and then add support in the language for the syntax bearophile described.<

Few weeks ago I have suggested to adopt a similar strategy in D too, for example to implement complex numbers, multi-precision numbers, and few other things. The compiler can implement just the syntax and a default "bridge" from such syntax and the logic that is contained into a normal D module of the std lib. So for example you can write:

Bigint a = 123_456_789_123_456_789_123_456_789;
Bigint b = 987_654_321_987_654_321_987_654_321;
c = a * b;

Where the compiler has a built-in support such natural syntax for Bigints literals (or complex number literals), but the operations like the multiplication among such Bigints are implemented in D into the std.bigints module and not into the compiler. I don't actually know if such idea can be implemented in a D compiler, no one has answered me about this so far :-)

---------------------

A possible small problem:
Floating-point linear ranges (as used in matlab and numPy, etc) are often useful, but the 0..10 and 0..10..2 syntax doesn't look much good if you want to re-use it for floats (or even for interval arithmetic) because all those points may be too much confusing, even if you add spaces:

0.0 .. 10.5 .. 0.5
0. .. 10. .. .5

So intervals may be kept for integral values only.

As an alternative syntax may be:

foreach (i; 1:10) {...}
foreach (i; 10 : 1 : -1) {...}
foreach (i; 0.0 : 10.5 : 0.5) {...}

Bye,
bearophile
February 27, 2009
bearophile wrote:
> As an alternative syntax may be:
> 
> foreach (i; 1:10) {...}
> foreach (i; 10 : 1 : -1) {...}
> foreach (i; 0.0 : 10.5 : 0.5) {...}

I have an idea - we define a contextual keyword "iota" that helps us specify from, to, and stride. Then we can write:

foreach (i; iota(0.0, 10.5, 0.5)) {...}


Andrei
February 28, 2009
On 2009-02-27 08:44:31 -0500, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> said:
> 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.

Nothing, really. Specifying intervals as two separate function arguments as in f(a, b) is perfectly acceptable. Having a struct Interval { int a, b } is also a nice way to define and pass an interval arround. And defining intervals as a..b in foreach and when slicing array is a pretty neat syntax.

But while all these solutions for passing intervals are nice, having an incoherent mix of all these isn't. I think intervals should be uniformized, and that probably goes by openning the a..b syntax to other uses. That could be done easily by mapping a..b to a struct in the standard library.

That said, as others have pointed out, it also opens some new possibilities:

It makes it easier to support multidimentional arrays. For instance, marray[1..3, 3, 3..4] could translate to a call to marray.opSliceIndex(interval!(int), int, interval!(int)).

Making intervals a type generalizes them as ranges. For instance you could create a "random" template function that chooses a random element in a random-access range: random(range), then use that same function with an integer interval: random(1..10). That's an improvement over having two arguments: random(1, 10), because a two-arguments tuple can't be treated as a range (with front, back, popFront, popBack, etc.). And while having random(interval(1, 10)) is technically the same, it also is a lot more cluttered.

If the language can avoid the clutter in foreach and array slices, then why can't we avoid it elsewhere? Why does an interval when inside a language construct looks better than elsewhere? That's the oddities mapping a..b to a standard type usable everywhere would avoid.

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

February 28, 2009
On 2009-02-27 09:47:30 -0500, Daniel Keep <daniel.keep.lists@gmail.com> said:

>> 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.

Well, if a..b simply maps to an interval type in the standard library (as I think it should) then you can implement the "in" operator in the standard library, outside of the language, if you want.

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

February 28, 2009
Michel Fortin wrote:
> On 2009-02-27 09:47:30 -0500, Daniel Keep <daniel.keep.lists@gmail.com> said:
> 
>>> 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.
> 
> Well, if a..b simply maps to an interval type in the standard library (as I think it should) then you can implement the "in" operator in the standard library, outside of the language, if you want.

I think a lot of my uneasiness with having intervals in the language is the syntax.

Yes, a..b is very nice.  It's also a bad syntax for intervals.  As Don keeps pointing out, you can't have an interval that includes int.max with that syntax.

Also, if you say to someone, "we have intervals, look: [a..b]!" I'd be surprised if a good proportion of people didn't assume that it was inclusive both sides.  That's how it'd be interpreted by math people.

Using ... to indicate an inclusive range doesn't solve the problem.  How
do you do (a,b]?  Introduce yet another operator?  What about (a, b)?

The only syntax I've ever liked for ranges is the math syntax.  Sadly, it's pretty horrible for a programming language, and I don't think it's even context free.

a..b in slicing and foreach is OK because it's for a very specific, well-understood purpose.  It's like how range(5) in Python generates the sequence [0, 1, 2, 3, 4] because 90% of the time, that's what you want in the circumstances where it's commonly used.

The argument that you can now easily test to see if a number is in a range is also a little underwhelming.  It's not THAT hard to write the test out manually.

On the whole, I think the use cases for this sort of syntax are just far too few and specialised to warrant syntax for them, especially when the syntax proposed is very restrictive.

Especially when you can do it in a library without too much effort.
Hell, my first instinct was to alias inter to Z to make it even shorter.
 Then it's only 3 characters more than native syntax, and more flexible.

I understand the desire to see something potentially useful in the language.  I just don't want to see D become like Perl (or to an extent, C++) where every feature that's even a little useful gets added, and we end up with a language where everyone has to learn and program in "subsets" of it, because no normal person could possibly remember how the whole thing works.

  -- Daniel
February 28, 2009
On Sat, 28 Feb 2009 02:33:15 +1100, Daniel Keep wrote:

> Andrei Alexandrescu wrote:
>> Daniel Keep wrote:
>>> 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):
>> 
>> This is pretty darn cool!
>> 
>> Andrei
> 
> Thanks.
> 
> On the off chance anyone wants to actually use it, I hereby release the interval module attached to my previous post into the public domain.
> 
> Anyway, it's 2:30 am here, so I'm off to bed.  :P
> 
>   -- Daniel

Are you even allowed to release code to public domain that late at night? :D
February 28, 2009
On 2009-02-27 23:02:30 -0500, Daniel Keep <daniel.keep.lists@gmail.com> said:

> On the whole, I think the use cases for this sort of syntax are just far
> too few and specialised to warrant syntax for them, especially when the
> syntax proposed is very restrictive.
> 
> Especially when you can do it in a library without too much effort.
> Hell, my first instinct was to alias inter to Z to make it even shorter.
>  Then it's only 3 characters more than native syntax, and more flexible.

But what I'm proposing is that a..b just become a shortcut for inter[a..b], or whatever is the standard interval struct in the standard library. Isn't that exactly what you're trying to do (a shortcut) by shortening inter[a..b] to Z[a..b] ?

> Using ... to indicate an inclusive range doesn't solve the problem.  How
> do you do (a,b]?  Introduce yet another operator?  What about (a, b)?

Perhaps using "..." was a bit too much to ask. But there is no reason why the standard library interval cannot support all kinds of bounds just like inter does. The syntax just won't be as nice as a..b.

Basically, all the compiler would do is map a..b to inter[a..b] and leave everything else to the standard library. So it's not removing any option, it just consistently allow you to write a..b whatever the place.

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