Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I've been thinking on how I would go about designing the "perfect" range literal if I were to do design a language. For you that don't know know a range literal in a statement that represents a range of values, like 1-4. They're most commonly used in for/foreach loops

There seems to a couple of issues one should take into account

  • Support for inclusive and exclusive ranges, tacking on +1 or -1 to endpoints seems a bit fugly and errorprone.

  • Support for stepping, so you can make a range of even or odd numbers for instance

  • Readability, it should be readily apparent what the range literal describes

  • Unambiguity, it should be perfectly unambigious what the range literal describes

  • The default should probably be from inclusive to exclusive since that's what's used in most cases for looping over arrays etc.

Anyways, one example of range literal I've seen is Ruby which is in the form of 1..3 for an exclusive (on the end) range and 1...3 for inclusive (on the end). You can also do 1..10.step(5). After careful consideration I found however a couple of things I didn't like about that approach (from my limited knowledge of ruby)

  1. You can only describe inclusive and exclusive for the end. While describing most scenarios it does seem a little inconsistent.

  2. Varying by just an additional . seems like a recipe for making it hard to see whether a range is inclusive or exclusive. I don't know about you but dot's tend to become something of a blur :)

  3. Adding method like notation for ranges seems to mix the notion of a literal with that of a class which seems a bit inconsistent (even if ranges get compiled to a class)

Anyways, after pondering different alternatives. I came up with this

  • [5..1] 5,4,3,2,1
  • [1..5[ 1,2,3,4
  • ]1..5] 2,3,4,5
  • [0..5..20] 0,5,10,15,20

and so forth. I like it because [ normally denonates a set and this kinda fits into that, even though this in contrast to a set would be ordered.

One thing I'm a bit torn about though is making the exclusive/inclusive indicators mandatory or not, ie if you write just 1..5 the default would be 1,2,3,4 since it's the most common case with arrays etc. It's easier and more readable, but less specific and if you had to write [1..5[ you learn early about how they work.

So what do you think, did I cover most bases, overlook something? would you make the [] mandatory? Would you design range literals differently in your programming language?

Candidates

  • bracket style: [0..10[ , with step: [0..5..20[
  • interval notation: [0..10) with step: [0..5..20)
  • exclamation for exlusive. 0..!10, with step: 0..5..!20
    • with different step. 0..!20, 5
    • however, that would make the default *0..10' inclusive-inclusive
  • wordy: [0 to !20 by 5]

I must say that my favorite aestically so far is 0..!10 and 0..5..!20, I just wish the default 0..10 to inclusive-exclusive would be more logical

share|improve this question
2  
1,5,10,15,20 Gap 4, 5, 5, 5?! –  Peter Taylor Apr 4 '11 at 12:19
    
oops :) corrected –  konrad Apr 4 '11 at 12:45
add comment

11 Answers

up vote 0 down vote accepted

And the winner is

Sorry for chosing my own solution, I really appreciate all the comments and feedback and please do critize this solution if you find anything wrong with it

So I decided to go with:

0..5           = 0,1,2,3,5
0..5..10       = 0,5,10
..3            = 0,1,3
!0..!5         = 1,2,3,4
3..            = 3 to infinity

segmented ranges
-
0..5,..5..20   = 0,1,2,3,4,5,10,15,20
0..3,10..5..20 = 0,1,2,3,10,15,20

As you can see inclusive is the default on both senses, it's what makes intuitively most sense especially when using stepping. You can use ! to make an end exclusive.

The downside is that normally you want to use exclusive when working against an array, but then again, most times you should probably be using something like for-each in those cases. If you really really need to work against the index the parser could recognize when you possibly forgot the exclusion marker on the end and issue a warning

I went with using .. on both sides of the step variable instead of using comma or anything else since that's what looked cleanest and I hope is the most readable.

I also threw in some syntax with comma's to be able to make ranges where different parts have different steps

share|improve this answer
    
It seems okay except when specifying the step. I think that would be cleaner as [0..10 by 5]. Segmented range could be [0..5, ..20 by 5], etc. One could also specify [numitems from first by step], which (unlike the other forms) could allow for a step size of zero. –  supercat Feb 16 at 23:01
add comment

One solution which I'd certainly consider is using operator overloading to allow e.g.

1+[0..3]*5

It wouldn't necessarily be immediately transparent to everyone, but once they stop and think I hope most programmers would understand it, and people who use the language regularly would simply learn it as an idiom.

To clarify, the result would be [1, 6, 11, 16], lifting the multiplication and then the addition over the range. I hadn't realised that it might be ambiguous to Python users; I think of ranges as subsets of total orders rather than as lists. and repeating a set doesn't make sense.

share|improve this answer
2  
Very ambiguous. list expression * 5 could also mean "repeat the value of list expression 5 times", as it does in Python. –  nikie Apr 4 '11 at 17:49
    
This is very odd looking and I'm not sure what this would be... 1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3? 1,3,6,9,12? 5,10,15? something else, perhaps? Either way, I find this notation completely counterintuitive. It looks like it's maybe some sort of strange C pointer operation... –  FrustratedWithFormsDesigner Apr 4 '11 at 20:14
add comment

What about something like this:

  • [5..1] 5,4,3,2,1
  • [1..5][i,e] 1,2,3,4
  • [5..1][i,e] 5,4,3,2
  • [1..5][e,i] 2,3,4,5
  • [1..5][e,e] 2,3,4
  • [0..20] by 5 0,5,10,15,20

The i and e in the second pair of square brackets indicates if the begining or ending of the range is inclusive or exclusive (or you could use incl, and excl if you want to be more clear). the by 5 indicates the stepping interval. The first and last example are inclusive of the first and last value, so I omitted the [i,i], which I think would be an OK assumed default behaviour.

Default values could be [i,i] for the inclusive/exclusive specifier and by 1 for the step specifier.

Normally I'd have suggested the standard notation involving ( and [ as mentioned by @Dan McGrath , but I agree that in code this could look confusing.

share|improve this answer
    
Can anyone explain the downvote? –  FrustratedWithFormsDesigner Apr 4 '11 at 14:06
    
I didn't see anything patently wrong. I upvoted to counteract the downvote. –  Berin Loritsch Apr 4 '11 at 14:45
add comment

Have you seen Haskell ranges ? Their idea for steps is similar to your (in the middle) but indicated with a syntactic difference (from @luqui's answer):

[1,2..10] = [1,2,3,4,5,6,7,8,9,10]
[1,3..10] = [1,3,5,7,9]
[4,3..0]  = [4,3,2,1,0]
[0,5..]   = [0,5,10,15,20,25,30,35...  -- infinite
[1,1..]   = [1,1,1,1,1,1,1,1,1,1,1...  -- infinite

I find this notation very intuitive: you start enumerating and skip over the body to mark where it should end.

It does not cover the case for "exclusive", but frankly, I'd rather be clear about the behavior once (specify which bound is inclusive and which is exclusive), and expects user to make adjustements unless the syntax is extremely clear (and does not confuse language agnostic editors).

share|improve this answer
    
+1: Simple, intuitive way to specify range and step. –  user1249 Apr 4 '11 at 20:21
add comment

Ruby has notation and a Range object that works. Essentially it looks like this:

1..5  # range 1,2,3,4,5

With Ruby, step size is not a function of the range, but rather a function of iterating through the range. We could use the same range above and iterate through it differently if we wanted to:

(1..5).step(3) { |x| puts x }
(1..5).step(2) { |x| puts x }

So here are things you may want to consider:

  • Adding language support for inclusive/exclusive endpoints can be problematic. If all ranges are inclusive (Ruby's choice), then the developers will adjust the endpoints of the range accordingly. How do you represent the endpoints inclusiveness or exclusivity in a way that is both visually unambiguous and unambiguous with a simple parser grammar?
  • Are you using the range for more than just iterating? Common examples include range comparisons, splicing, value in range comparisons. If so, how do you represent those possibilities? (See the link to the Range class for ideas to each of these examples.)

BTW, ranges are rather nice if you allow them to be included in a switch statement like Ruby does:

switch(myval)
    when 0..33 then puts "Low"
    when 34..66 then puts "Medium"
    when 67..100 then puts "High"
end

I'm not saying that Ruby's range object is the end all and be all, but it's a working implementation of the concept you are talking about. Play with it to see what you like, dislike, and would do differently.

share|improve this answer
    
Read the question carefully, OP is already aware of Ruby ranges: Anyways, one example of range literal I've seen is Ruby which is in the form of 1..3 for an exclusive (on the end) range and 1...3 for inclusive (on the end). You can also do 1..10.step(5). –  FrustratedWithFormsDesigner Apr 4 '11 at 15:05
add comment

I wouldn't use the '[1..5[' form for one of the same reasons you would avoid mixing parens and braces - it will confuse most existing editors' brace matching. Perhaps use a marker inside the braces, like '[1..|5]'.

The other thing to consider is the behavior of methods to get the limits. For instance, 'begin'/'end' vs. 'first'/'last' vs. 'min'/'max'. They need to be sane for both inclusive and exclusive limits, as well as those with a step size.

share|improve this answer
add comment

I believe your notation is far away from being unambigous. Intuitively, [0..5..20] doesn't look to me like a range with step width = 5. It even fails in some corner cases: what about expressing the set (0,2) - [0..2..2] or what should I put in the middle?

I think that Python's slice notation is a solid approach: [start:end:step] (step being optional).

If you really need support for exclusive and inclusive ranges, I'd use standard range notation (i.e. using ( and [) unless this introduces syntactic ambiguities in your language.

share|improve this answer
1  
regarding the "standard" notation, European schools teach [], [[, ]] and ][, no parenthesis... and our standard is better, of course ;) –  Matthieu M. Apr 4 '11 at 19:39
    
@Matthieu: Actually, here in the Netherlands (which is in Europe), we were also taught that standard notation. –  Frits May 4 '11 at 17:30
add comment
  • [1..5[ 1,2,3,4
  • ]1..5] 2,3,4,5
  • ]1..5[ 2,3,4

Why not just use [1..4], [2..5], [2..4]? Excluding Ranges don't offer much benefit. You don't need to write MIN+1 or MAX-1, yes, but they don't prohibit it anyway. Too much variation, imho. My language of choice, scala, has (1 to 3) and (1 until 3) [=(1 to 2)] but it just confused me at the beginning. Now I always use 'x to y' and know therefore, that the option which I don't use is the excluding one, but of course I don't benefit from an Option I don't use.

  • [5..1] 5,4,3,2,1

is of course (1 to MAX) (x => y = (MAX + 1) - x) but that is much boilerplate and not userfriendly.

  • [0..5..20] 0,5,10,15,20

is of course (0 to 4) (x => y = x * 5) is not so much boilerplate. Disputable.

share|improve this answer
    
The main benefit of excluding ranges, afaik, is that the number of elements is the difference of the endpoints. –  Justin L. Nov 22 '13 at 23:20
    
@JustinL.: 5-1 is 4 in my algebra but contains 3 elements, excluding the boundaries: 2, 3, 4. –  user unknown Nov 23 '13 at 3:49
add comment

You should read about list comprehensions in languages that offer them.

Python, for example, covers your cases pretty nicely with the range() function and the list comprehension for cases too complex to express with range().

share|improve this answer
add comment

Why invent what already exists in Mathematics?

Interval Notation

That should cover your 'Unambiguity' point as it already exists.

Your only issue would be defining the stepping. Maybe something else in mathematics can help with that?

share|improve this answer
2  
Hi, I've actually about interval notation. However what made me not want to use it was the fact that it wasn't symmetric with both [ and ) and used paranthesis as well as not having steps (as you mention). Using paranthesis seems to have the potential to confuse brace matching and I'd rather save them to expressions. Using ] and [ is a iso standard and seemed to go better with integrating stepping, but that's just my opinion of course. –  konrad Apr 4 '11 at 11:11
6  
@MKO: Your idea of using unmatched brackets (like [1..5[) will confuse editors at least as much as standard interval notation. –  David Thornley Apr 4 '11 at 14:08
1  
Hmm, another idea, what about using ! for, ie: 1..!5 or 0..5..!20 (with stepping) –  konrad Apr 4 '11 at 14:19
1  
@MKO: Using ! to exclude the first or last element could be good. –  FrustratedWithFormsDesigner Apr 4 '11 at 14:44
add comment

I think when you're setting a third increment value you're better off adding this a the end, rather than the middle, since this is an optional value, and seems more intuitive to seasoned programmers than adding an optional 3rd argument in the middle.

so instead of [1..5..20] you could do something like [1..20][5] or [1..20,5]

share|improve this answer
    
That's a valid point and perhaps a matter of taste, it just seemed to me that it would be easier to think "1 step 5 to 20" rather than, "from 1 to 2, with step 5" using [1..20][5] seems a bit cluttered but perhaps the comma approach would be viable. I'd appreciate more feedback on that –  konrad Apr 4 '11 at 12:49
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.