This page is no longer maintained — Please continue to the home page at www.scala-lang.org

Re: No doubt it's much too late now, but wouldn't it be nice to have negative indices in sequences?

12 replies
jibal
Joined: 2010-12-01,
User offline. Last seen 1 year 45 weeks ago.

On Wed, May 18, 2011 at 2:17 PM, Jim Balter wrote:
> On Wed, May 18, 2011 at 1:09 PM, Gregory Crosswhite
> wrote:
>> On 5/18/11 12:42 PM, martin odersky wrote:
>>
>> On Wed, May 18, 2011 at 9:24 PM, Gregory Crosswhite
>> wrote:
>>>
>>> On 5/18/11 9:38 AM, Ken McDonald wrote:
>>>>
>>>> Like, where, seq(-1) is the last element of the sequence, etc. Python
>>>> does things this way, and it's a really nice convenience.
>>>>
>>>> Ken
>>>
>>> I agree with you.  It is probably too late to change apply(), but perhaps
>>> a new method could be created, such as .at(-1)?
>>
>> No. We are now at a point where we need to treat collections as more or less
>> fixed. No new methods just for nicety's sake. Sometimes I envie Java for its
>> imposed rigidity. Nobody proposes new methods for Java collections because
>> the price to pay (re-implement all implementations) is obviously too high.
>> In Scala, the costs of bloat and change are less obvious but nevertheless
>> real.
>>
>> Cheers
>>
>>  -- Martin
>>
>>
>>
>> Would it really be that difficult to implement, though?
>
> Who cares? Littering the library with method names with superset
> functionality of other methods is a horrid idea. There's nothing in
> the name "at" that suggests that its functionality is a superset of
> "apply". How will you explain this to your grandchildren?
>
>> It seems to me
>> (probably naively) that all it would take to implement it would be a single
>> addition to the scala.collection.Seq trait which would look something like:
>>
>>     def at(index: Int): A =
>>         if(index >= 0)
>>             apply(index)
>>         else
>>             apply(size()-1+index)
>
> You do realize that, if size() is O(N), that this is a grossly
> inefficient implementation, don't you? That there's an implementation
> that only requires one traversal?
>

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: No doubt it's much too late now, but wouldn't it be nice to
Implementation is usually the least of all costs, compared to documentation, packaging, feature interaction, cognitive load, future legacy cost, etc. So just because something's easy to implement it is not necessarily cheap.

Cheers

 -- Martin

On Wed, May 18, 2011 at 11:35 PM, Gregory Crosswhite <gcrosswhite@gmail.com> wrote:
On 5/18/11 1:21 PM, Sciss wrote:
also, what's so hard about writing seq(seq.size - 1) if you need to access the last element of an indexed sequence? i find seq(-1) quite esoteric to be part of and useful in a standard collections library.

Nothing, but that kind of argument can be used against every convenience library function...

Cheers,
Greg



--
----------------------------------------------
Martin Odersky
Prof., EPFL and CEO, Typesafe
PSED, 1015 Lausanne, Switzerland
Tel. EPFL: +41 21 693 6863
Tel. Typesafe: +41 21 691 4967

jibal
Joined: 2010-12-01,
User offline. Last seen 1 year 45 weeks ago.
Re: No doubt it's much too late now, but wouldn't it be nice to

On Wed, May 18, 2011 at 3:09 PM, Rex Kerr wrote:
> On Wed, May 18, 2011 at 5:52 PM, Jim Balter wrote:
>>
>> On Wed, May 18, 2011 at 2:35 PM, Gregory Crosswhite
>> wrote:
>> > On 5/18/11 1:21 PM, Sciss wrote:
>> >>
>> >> also, what's so hard about writing seq(seq.size - 1) if you need to
>> >> access
>> >> the last element of an indexed sequence? i find seq(-1) quite esoteric
>> >> to be
>> >> part of and useful in a standard collections library.
>> >
>> > Nothing, but that kind of argument can be used against every convenience
>> > library function...
>>
>> Which methods currently in the library save the typing of 8
>> characters?
>
> That's not a serious question is it?
>
> x.filter(!f(_))
> x.filterNot(f)  // Saves one character!
>
> x.insertAll(5, Seq('a', 'b'))
> x.insert(5, 'a', 'b')  // Saves 8 characters--5 if it didn't take the
> "insert" name!
>
> !x.isEmpty
> x.isDefined  // Saves -1 character!
>
> Range(0, x.size)
> x.indices  // Saves 7 characters!
>
> x.foldLeft(0)(_ max _)
> (0 /: x)(_ max _)  // Saves 5 characters!
>
> And so on.
>
> The library is full of things that save typing a few characters, or save
> needing to read a ! or two, or provide a specific implementation rather than
> a general one.

Rex, this is one of the many times when you have replied to me that I
have felt inclined to say "whoosh!". Most of these features are not
there *only* to save a few characters, just because you can come up
with examples where they do.

Seth Tisue
Joined: 2008-12-16,
User offline. Last seen 34 weeks 3 days ago.
Re: No doubt it's much too late now, but wouldn't it be nice to

>>>>> "Jim" == Jim Balter writes:

Jim> P.S. Your arithmetic is wrong.

Which ironically kind of supports his thesis, actually. Since it shows
that you can never trust yourself, or anyone else, to get index
arithmetic right so we'd better provide safer methods :-)

However:

The negative index thing seems prone to off-by-one-errors to me, since
the left end is 0-indexed but the right end must be 1-indexed since
there's no negative zero (not as an Int, anyway).

Also, if I use an index that's too large, does that wrap around, too?

Plus it violates the fail-fast principle. If I accidentally use a
negative index I'd like an error, please, and right away!

Basically the wraparound thing is superficially appealing but actually
pretty iffy and problematic. Just like the rest of Python :-)

Anyway, we have reverse and takeRight and many other methods, so there's
plenty of other amusing possibilities not involving index arithmetic,
e.g.

scala> (1 to 10).takeRight(2).head
res2: Int = 9

scala> ((1 to 10).reverse)(2)
res3: Int = 8

Gregory: in time, you may come to find the "casual hostility"
invigorating. We don't mean it, except Jim Balter who does.

Raoul Duke
Joined: 2009-01-05,
User offline. Last seen 42 years 45 weeks ago.
Re: No doubt it's much too late now, but wouldn't it be nice to

On Wed, May 18, 2011 at 3:33 PM, Seth Tisue wrote:
> Basically the wraparound thing is superficially appealing but actually
> pretty iffy and problematic.  Just like the rest of Python :-)

word.

jibal
Joined: 2010-12-01,
User offline. Last seen 1 year 45 weeks ago.
Re: No doubt it's much too late now, but wouldn't it be nice to

On Wed, May 18, 2011 at 3:33 PM, Seth Tisue wrote:
>>>>>> "Jim" == Jim Balter writes:
>
>  Jim> P.S. Your arithmetic is wrong.
>
> Which ironically kind of supports his thesis, actually.  Since it shows
> that you can never trust yourself, or anyone else, to get index
> arithmetic right so we'd better provide safer methods :-)
>
> However:
>
> The negative index thing seems prone to off-by-one-errors to me, since
> the left end is 0-indexed but the right end must be 1-indexed since
> there's no negative zero (not as an Int, anyway).
>
> Also, if I use an index that's too large, does that wrap around, too?
>
> Plus it violates the fail-fast principle.  If I accidentally use a
> negative index I'd like an error, please, and right away!
>
> Basically the wraparound thing is superficially appealing but actually
> pretty iffy and problematic.  Just like the rest of Python :-)
>
> Anyway, we have reverse and takeRight and many other methods, so there's
> plenty of other amusing possibilities not involving index arithmetic,
> e.g.
>
> scala> (1 to 10).takeRight(2).head
> res2: Int = 9
>
> scala> ((1 to 10).reverse)(2)
> res3: Int = 8
>
> Gregory: in time, you may come to find the "casual hostility"
> invigorating.  We don't mean it, except Jim Balter who does.

Since there's no smiley, you seem to be seriously making some claim,
but I can't make out exactly what it is. I can tell you that you know
a lot less about me than you think you do.

ichoran
Joined: 2009-08-14,
User offline. Last seen 2 years 3 weeks ago.
Re: No doubt it's much too late now, but wouldn't it be nice to
On Wed, May 18, 2011 at 6:22 PM, Jim Balter <Jim@balter.name> wrote:
On Wed, May 18, 2011 at 3:09 PM, Rex Kerr <ichoran@gmail.com> wrote:
> On Wed, May 18, 2011 at 5:52 PM, Jim Balter <Jim@balter.name> wrote:
>>
>> On Wed, May 18, 2011 at 2:35 PM, Gregory Crosswhite
>> <gcrosswhite@gmail.com> wrote:
>> > On 5/18/11 1:21 PM, Sciss wrote:
>> >>
>> >> also, what's so hard about writing seq(seq.size - 1) if you need to
>> >> access
>> >> the last element of an indexed sequence? i find seq(-1) quite esoteric
>> >> to be
>> >> part of and useful in a standard collections library.
>> >
>> > Nothing, but that kind of argument can be used against every convenience
>> > library function...
>>
>> Which methods currently in the library save the typing of 8
>> characters?
>
> That's not a serious question is it?
>
> x.filter(!f(_))
> x.filterNot(f)  // Saves one character!
>
> x.insertAll(5, Seq('a', 'b'))
> x.insert(5, 'a', 'b')  // Saves 8 characters--5 if it didn't take the
> "insert" name!
>
> !x.isEmpty
> x.isDefined  // Saves -1 character!
>
> Range(0, x.size)
> x.indices  // Saves 7 characters!
>
> x.foldLeft(0)(_ max _)
> (0 /: x)(_ max _)  // Saves 5 characters!
>
> And so on.
>
> The library is full of things that save typing a few characters, or save
> needing to read a ! or two, or provide a specific implementation rather than
> a general one.

Rex, this is one of the many times when you have replied to me that I
have felt inclined to say "whoosh!". Most of these features are not
there *only* to save a few characters, just because you can come up
with examples where they do.

Bill Atkins
Joined: 2011-05-10,
User offline. Last seen 42 years 45 weeks ago.
Re: No doubt it's much too late now, but wouldn't it be nice to
Rex Kerr wrote:
BANLkTin09SVMw7GyHo6xME6dFC1rFQr+1w [at] mail [dot] gmail [dot] com" type="cite"> At would be more efficient for many collections, more compact to write, and it's clearer what's going on once you know the notation.  All slightly nice, at the cost of library complexity and maintenance.

There really isn't any difference here between at as a nicety method and a bunch of other nicety methods that have a modest advantage in clarity or efficiency or whatnot.

Personally, I think a lot of methods should be (eventually) removed, or better yet, moved from the core collection into "styles" that you could get from importing the right implicit conversions.  But if you're arguing that "at" is of a fundamentally different nature than my examples, you should be more explicit about what you think the difference is.

  --Rex


I second what Seth said about failing fast - I expect Seq.apply to fail if I give it an out-of-bounds index.

At this point, we already have Seq.apply which has had a particular behavior for eight years. We can't change Seq.apply's contract for this (no existing Scala code has been written with this possibility in mind) and it makes little sense to add a separate, parallel method called at that is "almost like Seq.apply but allows negative indices."

Use an implicit conversion if you need this functionality. As the OP said, "it's much too late now."

Ken McDonald
Joined: 2011-02-13,
User offline. Last seen 42 years 45 weeks ago.
Re: No doubt it's much too late now, but wouldn't it be nice to

>
> The negative index thing seems prone to off-by-one-errors to me, since
> the left end is 0-indexed but the right end must be 1-indexed since
> there's no negative zero (not as an Int, anyway).

Python had a partial solution for slices (very imperfect, I admit). Doing
something like seq[-3:] would return the last three items of a sequence.
So "nothing" represented "-0" so to speak. The obvious problem was that
you couldn't pass in an index to represent that. But comparing that to
seq[-3:seq.length] (or whatever it was, my Python is pretty rusty right now)
did a small but measurable amount in reducing errors.
>
> Basically the wraparound thing is superficially appealing but actually
> pretty iffy and problematic. Just like the rest of Python :-)
>
I'm glad the smiley is there. Python is certainly not perfect (hey, I'm using Scala
now), but it was loads ahead of most of what else existed at the time. It's not
like I'm advocating Perl features.

Jim Powers
Joined: 2011-01-24,
User offline. Last seen 36 weeks 2 days ago.
Re: No doubt it's much too late now, but wouldn't it be nice to
Granted this is a contrived example, but wouldn't letting -1 for a Seq index silently hide nice off-by-one bugs that should really be reported as such?
val asWords = Vector("one","two","three","four")
def printLength(s:String) {  println("%s elements".format(asWords(s.length-1)))}
printLength("1")printLength("12") printLength("123")printLength("1234")printLength("") // <- oops!
Allowing -1 to be accepted as an index bounds would not raise an error but would instead print "four".

--
Jim Powers
Philippe Lhoste
Joined: 2010-09-02,
User offline. Last seen 42 years 45 weeks ago.
Re: No doubt it's much too late now, but wouldn't it be nice to

On 19/05/2011 00:33, Seth Tisue wrote:
> The negative index thing seems prone to off-by-one-errors to me, since
> the left end is 0-indexed but the right end must be 1-indexed since
> there's no negative zero (not as an Int, anyway).

Indeed, that's a strong argument against that.
While I find convenient to have a single character notation for such usage, I find this
often confusing in the languages I use (eg. Lua).
And somehow, using a sign bit to express a different meaning on the parameters feels
low-level, like an ASM or C trick, from a world where every bit counts in a 4KB memory and
a 1MHz processor (the kind I used when I started to program...).

Somehow, perhaps a better, more modern API could look like:
val v = seq(0, fromEnd = true) // Defaulting to false

One can argue against boolean parameters with default value (verbose, used only when
meaning the opposite of default, etc.) but at least, it is more readable that seq(0, true)
we can find in Java code...

d_m
Joined: 2010-11-11,
User offline. Last seen 35 weeks 2 days ago.
Re: No doubt it's much too late now, but wouldn't it be nice to

On Wed, May 18, 2011 at 11:28:25PM -0400, Jim Powers wrote:
> val asWords = Vector("one","two","three","four")
>
> def printLength(s:String) {
> println("%s elements".format(asWords(s.length-1)))
> }
>
> printLength("1")
> printLength("12")
> printLength("123")
> printLength("1234")
> printLength("") // <- oops!

Disclaimer: I am not advocating adding this feature to Scala.

That said, I like this feature in Python and I don't know what this
example proves. The code is equally valid (invalid?) if you use
"asWords(s.length)" which breaks currently *and* under the new
proposal... it has one broken case and 4 cases that don't break, just
like the original example. Given that the example can only work for 4
inputs, and breaks on all other inputs, it is pretty contrived.

I can imagine someone making this argument to show that people
shouldn't be using sequence indices, but should be using folds and
writing recursive functions. I don't see how it addresses the negative
indexing feature itself.

There is no shortage of rope in Scala, and the feature was not rejected
on the basis of its semantics, but rather because of the burden of
maintenance, documentation, and testing.

I find this feature very useful in languages where it is baked in from
the beginning (e.g. Python). I am not sure I'd find this feature useful
in a language where it was added on as an afterthought (e.g. Scala).

Jim Powers
Joined: 2011-01-24,
User offline. Last seen 36 weeks 2 days ago.
Re: No doubt it's much too late now, but wouldn't it be nice to
On Thu, May 19, 2011 at 10:41 AM, Erik Osheim <erik@plastic-idolatry.com> wrote:
That said, I like this feature in Python and I don't know what this example proves. The code is equally valid (invalid?) if you use
"asWords(s.length)" which breaks currently *and* under the new
proposal... it has one broken case and 4 cases that don't break, just
like the original example. Given that the example can only work for 4
inputs, and breaks on all other inputs, it is pretty contrived.

Fair enough, I never claimed it was anything other than contrived.  Suffice to say that off-by-one-errors are exposed when you leak out of something bounded buy 0..N-1 instead of living on in the new bounds of -N..N-1.
No contention on the "burden" arguments.
OTOH, if there is some sense that support of this particular use case is desirable then why not generalize it to mod N arithmetic and never get an out of bounds exception?  I do have many use cases where I do, in fact, do mod N-limited access into Seq-like things, but it would never dawn on me to ask that the library hide when I did the math wrong.  Me, personally, I consider an error on giving a Seq a negative value of more utility than the convenience of stepping through things in reverse.  If I want that I'll do as suggested elsewhere in the thread and mix-in something that will nicely give me that ability.  Just sayin'  

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland