#10 Slicing

brian Sun 11 Dec 2005

My next task is to tackle list slicing and mapping operators to method names. But first we need to make a decision on what kind of slicing we support and what syntax to choose.

Let's boil slicing into three flavors:

StartCount: slice given a start index and item count RangeInclusive: slice given a start index and inclusive end range RangeExclusive: slice given a start index and exclusive end range

Ruby syntax

x := [ 0, 1, 2, 3, 4 ]
x[2, 2]  -> [ 2, 3 ]    // StartCount
x[1..3]  -> [ 1, 2, 3]  // InclusiveRange
x[1...3] -> [ 1, 2 ]    // ExclusiveRange

And of course negative indices can be used to index from the the back of the list. If the left hand side is a list slice, then it does an in place replacement (like splicing). Python supports steps too, although I say we forget about that.

Looking at syntax various languages use:

Ruby             Python, Boo    Groovy           Math Notation
--------------   ----------     ------------     -------------
x[start,count]                  
x[start..end]                   x[start..end]    [start,end]
x[start...end]   x[start:end]   x[start..<end]   [start,end)

If we make Range a first class object in Ruby, the method overloads in Fan might looks something like:

operator [](int start, int count)
operator [](Range range)

I think ExclusiveRange slicing is the one we absolutely have to support. What is your opinions on supporting StartCount and InclusiveRange slicing? If go down the Ruby/Groovy path of making Ranges a first class object, then the expresssion "x..y" and "x...y" really just become literals for Ranges which has a certain elegance to it.

andy Sun 11 Dec 2005

Well you are right - I see no need to include both inclusive/exclusive. However - the start/count is very handy and alot cleaner for its use cases. So I vote we include both start/count and range exclusive. Not sure about the syntax - I guess it depends if we make Range a first class object.

brian Sun 11 Dec 2005

My thinking is that we just support slicing using a first class Range object and give Range the literal represenations of x..y and x...y like Ruby. Slicing using a start and length can be done using a normal method and won't be available as an operator. To summarize:

Literal "x..y" -> new Range(x, y)
Literal "x...y" -> new Range(x, y-1)

class Range
{
  Range(Int start, Int end)
  readonly Int start  // always inclusive
  readonly Int end    // always inclusive
}

class Str
{
  Int slice(Int i) {...}
  Str slice(Range r) {...}
}

class List
{
  Obj slice(Int idx) {...}
  void splice(Int idx, Obj val) {...}
  List slice(Range r) {...}
  void splice(Range r, List x) {...}
}

class Map
{
  Obj slice(Obj key) {...}
  Obj splice(Obj key, Obj val) {...}
}

andy Sun 11 Dec 2005

I'm definitely not against first class ranges - but are they really necessary? That was one of those things that sounded cool - but I wasn't sure it carried much real value. There isn't much difference here, personally:

foo := bar[3..6]
foo := bar.splice(3,6)

brian Mon 12 Dec 2005

Actually there is a huge difference in the elegance of the langauge, because you always model the a[b] as a.splice(b). As soon as you start introducing multiple parameters into operators it starts to get ugly. Even more ugly is when you start using something like .. to signify a special case of multiple parameters.

Remember ranges are also used extensively in for loops too in Ruby and Groovy, in place of the traditional C style for loop syntax:

for (int i=0; i<=10; ++i) {...}
for (Int i : 1..10) {...}

That works because Range provides an each(Closure) method.

I'm also thinking we work Range into enums potentially.

andy Mon 12 Dec 2005

Well yeah, thats what I mean - its pretty rare imo you do something like 1..10 - you are usually iterating based on a computed length or size value - which limits their real world use, unless I can do something like:

foo := bar[0..len]

brian Mon 12 Dec 2005

I don't get your example - in that case foo would be declared to contain the slice of bar starting at 0 to len inclusive.

Actually maybe your confusion is my terminology - it is incorrect for me to say that we support Range literals, because the start and end are really expressions. Really it is kind of a merge of literal/operation to a specific wrapper class. For example the RangeExpr AST node looks like this:

Range
{
  Expr start, end;
  boolean inclusive;
}

andy Mon 12 Dec 2005

Ok, so I can use expressions and variables in Ranges? That makes alot more sense then.

john Mon 12 Dec 2005

They aren't literals, they're more like macros. But as long as you're creating those macros, why not include all three types (StartCount, InclusiveRange, ExclusiveRange). They all translate well into what you have defined for Range.

I don't see how this Range applies to enums unless you're planning to enhance it with tags and maybe non-contiguous values.

brian Mon 12 Dec 2005

Well I am calling them literals in the same way that I am calling Lists and Maps literals (since that seems to be what the community calls them). But in all three cases you can use any expression for the actual items:

r := start() .. end()
l := [ a(), b(), a()+b() ]

StartCount doesn't fit because then we have to come up with some special symbol that isn't used else where. I can make it synatic sugar for Range.make(start, start+len) just like I do ... if you guys can come up with a good symbol to use.

andy Mon 12 Dec 2005

Is [start,count] so bad? I mean we already have the issue of "x := y[1]" and "x = y[1]".

Login or Signup to reply.