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

120 views
Skip to first unread message

Ken McDonald

unread,
May 18, 2011, 12:38:32 PM5/18/11
to scala-l...@googlegroups.com
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

Paul Phillips

unread,
May 18, 2011, 12:51:34 PM5/18/11
to scala-l...@googlegroups.com, Ken McDonald
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.

It's much too late for Seq.apply (a method I'd much rather get rid of
entirely to be honest: I am completely convinced it should only be on
IndexedSeq) and I was wontfixed on negative indices even in a narrow
context with specific motivation.

https://issues.scala-lang.org/browse/SI-4500
"splitAt should work with negative indices"

Gregory Crosswhite

unread,
May 18, 2011, 3:24:47 PM5/18/11
to scala-l...@googlegroups.com
On 5/18/11 9:38 AM, Ken McDonald wrote:

I agree with you. It is probably too late to change apply(), but
perhaps a new method could be created, such as .at(-1)?

Cheers,
Greg

martin odersky

unread,
May 18, 2011, 3:42:14 PM5/18/11
to scala-l...@googlegroups.com

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

Gregory Crosswhite

unread,
May 18, 2011, 4:09:45 PM5/18/11
to scala-l...@googlegroups.com
Would it really be that difficult to implement, though?  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)

Cheers,
Greg

Paul Phillips

unread,
May 18, 2011, 4:17:21 PM5/18/11
to scala-l...@googlegroups.com, Gregory Crosswhite
On 5/18/11 1:09 PM, Gregory Crosswhite wrote:
>> 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.
>
> Would it really be that difficult to implement, though?

It is unfortunate that it is unbecoming to say things like "whoosh" or
make references to the missing the point olympics. If today were
opposite day and those things were ununbecoming, then today would be
looking pretty good.

> def at(index: Int): A =
> if(index >= 0)
> apply(index)
> else
> apply(size()-1+index)

With the minor detail that it changes the semantics of every piece of
scala code on the planet.

Sciss

unread,
May 18, 2011, 4:21:51 PM5/18/11
to scala-l...@googlegroups.com, Gregory Crosswhite
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.

Gregory Crosswhite

unread,
May 18, 2011, 4:40:49 PM5/18/11
to scala-l...@googlegroups.com
On 5/18/11 1:17 PM, Paul Phillips wrote:
> It is unfortunate that it is unbecoming to say things like "whoosh" or
> make references to the missing the point olympics. If today were
> opposite day and those things were ununbecoming, then today would be
> looking pretty good.
>
>> > def at(index: Int): A =
>> > if(index>= 0)
>> > apply(index)
>> > else
>> > apply(size()-1+index)
> With the minor detail that it changes the semantics of every piece of
> scala code on the planet.

How exactly does adding a new method with a brand new name intentionally
chosen to *not* conflict with any existing method name change the
semantics of every piece of scala code on the planet?

Methinks I am not the one who has had a "whoosh" moment here...

Cheers,
Greg

Paul Phillips

unread,
May 18, 2011, 4:45:30 PM5/18/11
to scala-l...@googlegroups.com, Gregory Crosswhite
On 5/18/11 1:40 PM, Gregory Crosswhite wrote:
> How exactly does adding a new method with a brand new name intentionally
> chosen to *not* conflict with any existing method name change the
> semantics of every piece of scala code on the planet?
>
> Methinks I am not the one who has had a "whoosh" moment here...

I'll split it with you. You are still whooshing on the fact that the
implementation in isolation represents a negligible fraction of the
total cost to be borne. But being a fiery competitor, I wasn't going to
let that keep me off the track in today's olympic trials.

Alex Repain

unread,
May 18, 2011, 4:46:51 PM5/18/11
to scala-l...@googlegroups.com


2011/5/18 Gregory Crosswhite <gcros...@gmail.com>

On 5/18/11 1:17 PM, Paul Phillips wrote:
It is unfortunate that it is unbecoming to say things like "whoosh" or
make references to the missing the point olympics.  If today were
opposite day and those things were ununbecoming, then today would be
looking pretty good.

>       def at(index: Int): A =
>           if(index>= 0)
>               apply(index)
>           else
>               apply(size()-1+index)
With the minor detail that it changes the semantics of every piece of
scala code on the planet.

How exactly does adding a new method with a brand new name intentionally chosen to *not* conflict with any existing method name change the semantics of every piece of scala code on the planet?

One could think of a class inheriting the Seq trait where apply is overriden. Therefore an access by .at(-1) should also be overriden if a particular semantics must be conserved. If the user of an API uses .at() of that SeqChild class, thinking it would produce the same result as a regular apply() access, he would run into problems. That, indeed, is a semantic problem, although not obvious if you think only in terms of compilation compatibility.

Gregory Crosswhite

unread,
May 18, 2011, 4:52:29 PM5/18/11
to scala-l...@googlegroups.com

I have a suggestion. Rather than just saying "whoosh!" and alienating a
newcomer (namely myself) to this list who was genuinely curious and
looking forward to an explanation as to exactly what he is missing that
would make it so difficult to implement this method beyond his proposal,
why don't you actually explain what it is that you are getting at?

I am sufficiently familiar with programming and programming languages in
general to conceive of the possibility of cross-cutting concerns, but I
am not familiar enough with the Scala collection classes in particular
to be able to see what it is that I am missing that would make such a
seemingly simple addition to them as monstrously complicated as you all
seem to be making it out to be.

Cheers,
Greg

Gregory Crosswhite

unread,
May 18, 2011, 5:02:38 PM5/18/11
to scala-l...@googlegroups.com
That is certainly a fair counterpoint (and one that I had thought of and probably should have mentioned if I hadn't been so annoyed at the "woosh!" tone of the response and thus disinclined to give any ground in my response :-) ).  Clearly though this is a trade-off that the Scala community has been willing to make since methods have continued to be added.  Since new versions have tended to break compatibility with application code anyway, making changes to code to migrate to a new version is something to which people are accustomed.

Cheers,
Greg

Gregory Crosswhite

unread,
May 18, 2011, 5:11:50 PM5/18/11
to scala-l...@googlegroups.com
On 05/18/2011 01:46 PM, Alex Repain wrote:
> One could think of a class inheriting the Seq trait where apply is
> overriden. Therefore an access by .at(-1) should also be overriden if
> a particular semantics must be conserved. If the user of an API uses
> .at() of that SeqChild class, thinking it would produce the same
> result as a regular apply() access, he would run into problems. That,
> indeed, is a semantic problem, although not obvious if you think only
> in terms of compilation compatibility.

Oh wait, my brain was fogged and I see now that I did not fully process
your message before responding to it. :-) I was actually thinking in
terms of what would happen if someone had already created an "at" method
in their subclass with completely different behavior, but that wasn't
what you were getting at. Your point was that introducing an .at()
method requires that all subclasses obey the contract that the semantics
of .at() be conserved and thus they must override it if necessary in
order to make their interface have consistent behavior. This actually
is a point that I hadn't considered --- it is essentially the same point
I had in mind that changing a superclass changes the semantics of its
subclasses, but it is a different variation on this theme that I hadn't
considered --- so I appreciate you taking the time to mention it to me,
though it is hard to see how .at() could ever cause problems this way in
practice.

Cheers,
Greg

Paul Phillips

unread,
May 18, 2011, 5:17:37 PM5/18/11
to scala-l...@googlegroups.com, Gregory Crosswhite
It is likely the literally thousands of hours I have spent diagnosing
and fixing bugs which are consequences of the interaction of elements
which are trivial when considered in isolation has lowered my patience
for glib replies about how easy something would be to implement,
especially when in direct response to an email which has as its central
point that such costs exist. If you would really like to consider
examples we have hundred and hundreds of open bugs for your review (now
in a flashy new database) the majority of which are a direct result of
unanticipated permutations amongst the 2^N configurations of our beloved
N features.

Jim Balter

unread,
May 18, 2011, 5:17:23 PM5/18/11
to scala-l...@googlegroups.com
On Wed, May 18, 2011 at 1:09 PM, Gregory Crosswhite

<gcros...@gmail.com> wrote:
> On 5/18/11 12:42 PM, martin odersky wrote:
>
> On Wed, May 18, 2011 at 9:24 PM, Gregory Crosswhite <gcros...@gmail.com>
> 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?

-- Jim

Jim Balter

unread,
May 18, 2011, 5:22:18 PM5/18/11
to scala-l...@googlegroups.com

P.S. You arithmetic is wrong.

Gregory Crosswhite

unread,
May 18, 2011, 5:35:02 PM5/18/11
to scala-l...@googlegroups.com
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

unread,
May 18, 2011, 5:40:38 PM5/18/11
to scala-l...@googlegroups.com
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
--
----------------------------------------------
Martin Odersky
Prof., EPFL and CEO, Typesafe
PSED, 1015 Lausanne, Switzerland
Tel. EPFL: +41 21 693 6863
Tel. Typesafe: +41 21 691 4967

Sciss

unread,
May 18, 2011, 5:41:37 PM5/18/11
to scala-l...@googlegroups.com
convenient when? in very special cases maybe, but not generally. convenient is also to have mean, median, variance, standard deviation, histogram, choose (get me a random element), etc., and still they are not in the standard interface.

i don't see how your wrapped index is useful in general scenarios. i could equally argue that it should be clipped:

def at( idx: Int ) : T = apply( math.max( 0, math.min( size - 1, idx ))

and from looking at my code base i would argue that this case is much more likely than wanting the index to wrap.

best, -sciss-

Jim Balter

unread,
May 18, 2011, 5:42:05 PM5/18/11
to scala-l...@googlegroups.com

Really? Python has a stack of collection classes with varying
implementations and big-O performance? Or do you mean that Python's
"lists", actually vectors, with O(1) indexing and length, provide
this convenience?

-- Jim

Jim Balter

unread,
May 18, 2011, 5:52:02 PM5/18/11
to scala-l...@googlegroups.com

Which methods currently in the library save the typing of 8
characters? And to both of you: seq(seq.size - 1) takes twice as long
as the efficient implementation for sequences where size is O(N). The
equivalence holds in Python for its vectors; it does not hold
generally for Seqs ... and libraries should have efficient
implementations, especially for usages so common that they warrant
"convenience".

-- Jim

Gregory Crosswhite

unread,
May 18, 2011, 5:54:18 PM5/18/11
to scala-l...@googlegroups.com

Sheesh, I subscribe to many programming language lists, and I have never
seen such casual hostility like I have on this one...

Yes, clearly that is what he meant. Ergo, charitably, if such a feature
existed we would probably want it to be a feature only of IndexedSeq and
not of Seq in general.

Cheers,
Greg

Gregory Crosswhite

unread,
May 18, 2011, 6:05:20 PM5/18/11
to scala-l...@googlegroups.com
On 5/18/11 2:41 PM, Sciss wrote:
> convenient when? in very special cases maybe, but not generally.

Grr, unfortunately I can't think of examples off the top of my head, but
essentially there are times in algorithms where you are subtracting
numbers from indices and it is useful for them to just wrap around. So
a generalization of this feature is really not just accepting negative
indices but to take the index modulo the size.

It's one of those features that you miss when you don't have it but that
when you wonder how you could have lived without it when you do. :-)

> convenient is also to have mean, median, variance, standard deviation, histogram, choose (get me a random element), etc., and still they are not in the standard interface.
>

True, my point is just saying that the fact that you could accomplish
something by other means doesn't mean that it therefore doesn't belong
outside the library, but in retrospect I see that the grandparent post
was really saying something more along the lines that the benefits of
seq(-1) are not nearly strong enough to merit being a library function
so my reply was not a particularly good response and therefore I
actually officially retract it. :-)

> i don't see how your wrapped index is useful in general scenarios. i could equally argue that it should be clipped:
>
> def at( idx: Int ) : T = apply( math.max( 0, math.min( size - 1, idx ))
>
> and from looking at my code base i would argue that this case is much more likely than wanting the index to wrap.
>
> best, -sciss-

Hmm, that is a good point. In that case, I propose that *both* exist!

Thank you for replying in an insightful and non-hostile manner. :-)

Cheers,
Greg

Jim Balter

unread,
May 18, 2011, 6:05:18 PM5/18/11
to scala-l...@googlegroups.com

I'm not following this. Seq#apply has a contract, "returns the element
of this sequence at index idx, where 0 indicates the first element",
that any subclass must obey. Given that, and discounting his buggy
arithmetic, Gregory's naive implementation should yield the right
result for any subclass of Seq.

-- Jim

Jim Balter

unread,
May 18, 2011, 6:08:13 PM5/18/11
to scala-l...@googlegroups.com
On Wed, May 18, 2011 at 2:54 PM, Gregory Crosswhite
<gcros...@gmail.com> wrote:
> On 5/18/11 2:42 PM, Jim Balter wrote:
>>
>> On Wed, May 18, 2011 at 9:38 AM, Ken McDonald<ykke...@gmail.com>  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
>>
>> Really? Python has a stack of collection classes with varying
>> implementations and big-O performance? Or do you mean that Python's
>> "lists",  actually vectors, with O(1) indexing and length, provide
>> this convenience?
>>
>> -- Jim
>
> Sheesh, I subscribe to many programming language lists, and I have never
> seen such casual hostility like I have on this one...

Given the remarkable hostility in your own comment, I simply do not believe you.

> Yes, clearly that is what he meant.

WHOOSH!

> Ergo, charitably, if such a feature
> existed we would probably want it to be a feature only of IndexedSeq and not
> of Seq in general.

Who would want that? Not anyone sensible who recognizes the value of
consistency.

-- Jim

Rex Kerr

unread,
May 18, 2011, 6:09:34 PM5/18/11
to scala-l...@googlegroups.com
On Wed, May 18, 2011 at 5:52 PM, Jim Balter <J...@balter.name> wrote:
On Wed, May 18, 2011 at 2:35 PM, Gregory Crosswhite
<gcros...@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

Sciss

unread,
May 18, 2011, 6:10:12 PM5/18/11
to scala-l...@googlegroups.com
BTW: SuperCollider's collection classes have four methods:

at -- plain lookup; syntactic sugar is square brackets, e.g. seq[1]
clipAt -- clips index to 0 ... size-1
wrapAt -- does a module size to the index
foldAt -- does a ping-pong (e.g. an index of size gets mapped to size - 1, an index of size - 1 gets mapped to size - 2 etc.)

:)

best, -sciss-

Bill Venners

unread,
May 18, 2011, 6:11:28 PM5/18/11
to scala-l...@googlegroups.com
Hi All,

I'm not sure what WHOOSH means yet, but I just wanted to point out that anyone who likes that at method that takes a negative index can have their cake and eat it too with an implicit conversion. I actually quite liked that feature of Python myself, but it never occurred to me or felt like it was "missing" in Scala.

Bill
--
Bill Venners
Artima, Inc.
http://www.artima.com

Gregory Crosswhite

unread,
May 18, 2011, 6:11:44 PM5/18/11
to scala-l...@googlegroups.com
On 5/18/11 3:08 PM, Jim Balter wrote:
> On Wed, May 18, 2011 at 2:54 PM, Gregory Crosswhite
> <gcros...@gmail.com> wrote:
>> On 5/18/11 2:42 PM, Jim Balter wrote:
>>> On Wed, May 18, 2011 at 9:38 AM, Ken McDonald<ykke...@gmail.com> 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
>>> Really? Python has a stack of collection classes with varying
>>> implementations and big-O performance? Or do you mean that Python's
>>> "lists", actually vectors, with O(1) indexing and length, provide
>>> this convenience?
>>>
>>> -- Jim
>> Sheesh, I subscribe to many programming language lists, and I have never
>> seen such casual hostility like I have on this one...
> Given the remarkable hostility in your own comment, I simply do not believe you.

Okay, you all win. Clearly I am the one who does not belong on this
list. Have fun!

- Greg

Sciss

unread,
May 18, 2011, 6:14:15 PM5/18/11
to scala-l...@googlegroups.com
(sorry, some typos)

BTW: SuperCollider's collection classes have four methods:

at -- plain lookup; syntactic sugar is square brackets, e.g. seq[1]
clipAt -- clips index to 0 ... size-1

wrapAt -- does a modulo-size to the index
foldAt -- does a ping-pong (e.g. an index of size gets mapped to size - 1, an index of size + 1 gets mapped to size - 2 etc.)

:)

best, -sciss-

Jim Balter

unread,
May 18, 2011, 6:14:01 PM5/18/11
to scala-l...@googlegroups.com

To clarify: it would be a (nother) horrid idea for apply() or at() to
take negative arguments only for IndexedSeq and not for Seq. It would
not be a horrid idea for apply to be a method of IndexedSeq but not
Seq, as Paul urged (for another universe where Seq#apply doesn't yet
exist).

-- Jim

Jim Balter

unread,
May 18, 2011, 6:22:29 PM5/18/11
to scala-l...@googlegroups.com

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.

-- Jim

Seth Tisue

unread,
May 18, 2011, 6:33:00 PM5/18/11
to scala-l...@googlegroups.com
>>>>> "Jim" == Jim Balter <J...@Balter.name> 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.

--
Seth Tisue | Northwestern University | http://tisue.net
lead developer, NetLogo: http://ccl.northwestern.edu/netlogo/

Sciss

unread,
May 18, 2011, 6:34:32 PM5/18/11
to scala-l...@googlegroups.com
foldAt is probably a bad name as in scala fold has a predefined meaning. maybe bounceAt is better?

class RichIndexedSeq[ T ]( xs: IndexedSeq[ T ]) {
def wrapAt( idx: Int ) : T = {
val sz = xs.size
if( sz == 0 ) throw new IndexOutOfBoundsException()
val i = if( idx >= 0 ) idx % sz else (sz - 1) - ((-1 - idx) % sz)
xs( i )
}
def clipAt( idx: Int ) : T = xs( math.max( 0, math.min( xs.size - 1, idx )))
def bounceAt( idx: Int ) : T = {
val sz = xs.size
if( sz == 0 ) throw new IndexOutOfBoundsException()
val sz2 = (sz - 1) << 1
val i = if( idx >= 0 ) idx % sz2 else (sz2 - 1) - ((-1 - idx) % sz2)
val j = if( i < sz ) i else sz2 - i
xs( j )
}
}

implicit def enrichIndexedSeq[ T ]( xs: IndexedSeq[ T ]) = new RichIndexedSeq[ T ]( xs )

val x = Vector(0,1,2,3)
val i = (-10 to 10)
i.map( x.wrapAt( _ ))
i.map( x.clipAt( _ ))
i.map( x.bounceAt( _ ))

best, -sciss-

Raoul Duke

unread,
May 18, 2011, 6:37:17 PM5/18/11
to scala-l...@googlegroups.com
On Wed, May 18, 2011 at 3:33 PM, Seth Tisue <se...@tisue.net> wrote:
> Basically the wraparound thing is superficially appealing but actually
> pretty iffy and problematic.  Just like the rest of Python :-)

word.

Sciss

unread,
May 18, 2011, 6:41:23 PM5/18/11
to scala-l...@googlegroups.com
def foldAt( idx: Int ) : T = {

val sz = xs.size
if( sz == 0 ) throw new IndexOutOfBoundsException()
val sz2 = (sz - 1) << 1
val i = if( sz2 == 0 ) 0 else if( idx >= 0 ) idx % sz2 else (sz2 - 1) - ((-1 - idx) % sz2)

val j = if( i < sz ) i else sz2 - i
xs( j )
}

Jim Balter

unread,
May 18, 2011, 6:41:58 PM5/18/11
to scala-l...@googlegroups.com
On Wed, May 18, 2011 at 3:33 PM, Seth Tisue <se...@tisue.net> wrote:

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.

-- Jim

Rex Kerr

unread,
May 18, 2011, 7:04:16 PM5/18/11
to scala-l...@googlegroups.com

Let's try a concrete example:
  x.filter(_ > 5).at(-2)
  x.filter(_ > 5).takeRight(2)(0)
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


Bill Atkins

unread,
May 18, 2011, 7:19:10 PM5/18/11
to scala-l...@googlegroups.com
Rex Kerr wrote:
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."

Kenneth McDonald

unread,
May 18, 2011, 9:57:53 PM5/18/11
to scala-l...@googlegroups.com
It's just the "code smell" that Martin talks about at various times. It's inelegant to have to repeat names simply because the designers of an API didn't go to the trouble to make their API more general. (NOTE: I'm NOT implying this is the case with Scala, though I have seen it many other places.) And from working quite extensively with Python over the years, I've found this little convenience to be, well, quite convenient.

I want to make clear I wasn't implying changing any parts of the Scala library to support this--I hope that was clear from the title of my original post. I'd just come across a situation where this would have been useful, and would curious as to other people's thoughts and why it had not originally been done.

Thanks,
Ken

Kenneth McDonald

unread,
May 18, 2011, 10:02:06 PM5/18/11
to scala-l...@googlegroups.com
No, of course I didn't mean that--I writing to a sophisticated audience with a pretty wide range of programming, and I don't feel the need to dot all my i's and cross all my t's unless I'm asking a very technical question. (And then I often get it wrong anyway :-) ). It was really just a casual observation about a feature which many years of use has led me to value.

Ken

Kenneth McDonald

unread,
May 18, 2011, 10:11:47 PM5/18/11
to scala-l...@googlegroups.com
>
> 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

unread,
May 18, 2011, 11:28:25 PM5/18/11
to scala-l...@googlegroups.com
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

Ittay Dror

unread,
May 19, 2011, 1:13:57 AM5/19/11
to scala-l...@googlegroups.com, martin odersky


martin odersky wrote:


On Wed, May 18, 2011 at 9:24 PM, Gregory Crosswhite <gcros...@gmail.com> 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.

Maybe have a way of easily extending the sdk with external libraries? E.g., the compiler as argument a list of jars where the classes are annotated as @Extending another class. Methods have as first argument objects of the extended class. So then to get an 'as' method I just pass a jar with its implementation to the compiler.

Similar to implicit conversions, but without the import and wrapping tax.

Ittay



Cheers

 -- Martin

Philippe Lhoste

unread,
May 19, 2011, 5:51:39 AM5/19/11
to scala-l...@googlegroups.com
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...

--
Philippe Lhoste
-- (near) Paris -- France
-- http://Phi.Lho.free.fr
-- -- -- -- -- -- -- -- -- -- -- -- -- --

Daniel Sobral

unread,
May 19, 2011, 8:16:48 AM5/19/11
to scala-l...@googlegroups.com
On Wed, May 18, 2011 at 19:11, Bill Venners <bi...@artima.com> wrote:
> Hi All,
>
> I'm not sure what WHOOSH means yet, but I just wanted to point out that
> anyone who likes that at method that takes a negative index can have their
> cake and eat it too with an implicit conversion. I actually quite liked that
> feature of Python myself, but it never occurred to me or felt like it was
> "missing" in Scala.

For that matter, I like that feature as well, and, if Scala had it
default like Python, I'd have spent much more time last Saturday
figuring out why my code wasn't working, when it was just a matter of
off-by-1 errors.

--
Daniel C. Sobral

I travel to the future all the time.

Erik Osheim

unread,
May 19, 2011, 10:41:13 AM5/19/11
to scala-l...@googlegroups.com
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).

-- Erik

Bill Venners

unread,
May 19, 2011, 10:43:56 AM5/19/11
to scala-l...@googlegroups.com
Hi Daniel,

On Thu, May 19, 2011 at 5:16 AM, Daniel Sobral <dcso...@gmail.com> wrote:
On Wed, May 18, 2011 at 19:11, Bill Venners <bi...@artima.com> wrote:
> Hi All,
>
> I'm not sure what WHOOSH means yet, but I just wanted to point out that
> anyone who likes that at method that takes a negative index can have their
> cake and eat it too with an implicit conversion. I actually quite liked that
> feature of Python myself, but it never occurred to me or felt like it was
> "missing" in Scala.

For that matter, I like that feature as well, and, if Scala had it
default like Python, I'd have spent much more time last Saturday
figuring out why my code wasn't working, when it was just a matter of
off-by-1 errors.

Yes, I think that's why I didn't miss it in Scala. I program differently in Scala than I did in Python, using indexes a lot less often.

Bill
 

Jim Powers

unread,
May 19, 2011, 11:37:11 AM5/19/11
to scala-l...@googlegroups.com
On Thu, May 19, 2011 at 10:41 AM, Erik Osheim <er...@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'
 
-- Erik

--
Jim Powers

Arioch

unread,
Jun 2, 2011, 8:15:28 AM6/2/11
to scala-l...@googlegroups.com
В письме от Thu, 19 May 2011 02:05:18 +0400, Jim Balter <J...@balter.name>
сообщал:

>> One could think of a class inheriting the Seq trait where apply is
>> overriden. Therefore an access by .at(-1) should also be overriden if a
>> particular semantics must be conserved. If the user of an API uses
>> .at() of
>> that SeqChild class, thinking it would produce the same result as a
>> regular
>> apply() access, he would run into problems.

> I'm not following this. Seq#apply has a contract, "returns the element
> of this sequence at index idx, where 0 indicates the first element",
> that any subclass must obey. Given that, and discounting his buggy
> arithmetic, Gregory's naive implementation should yield the right
> result for any subclass of Seq.

True - as long as it remains naive.
But then optimizations may be implemented, working directly with data
structure bypassing apply().
Say, in a couple of years, when everyone already forgotten this
discussion, and would be surprised by strange bugs.

---

And - already asked - why Seq ?

When u load a database table, it might be one-directional or
bi-directional.

Same way for sequences, fixed-size array (is it Python's Vector mentioned
above?) would be very easy and fast to implement at on top of apply.

For usual one-way List or Stack it would be not so efficient though.
So either use of .reverse() would be better or iterating all item of List
required.
But .reverse() is nice when u save (cache) the result, for at (-2),
at(-3), etc.
Should List cache it ? if yes, then when to dispose it ? What about
mutable List ?
Isolation vs optimisation, common trait :-D
If u implement negative indice ouside the class, in your code, you can
.reverse() the list and hold it as long as u need. But inside the List
class, how can the lib know what u try to do ?

I also think that non-effective method over List would be like provoking
to use it without thinking.

---

My guess, is that those wrapping, bouncing and clipping methods might be
nice for SOME sequences, with easy cheap access to any random item and
easy cheap access to current length.

Yet for Seq in general they would have to choose the implementation:
* either naive one, provoking slow ineffective coding
* or optimised complicated ones, including changing the internal data
structures as well not only code, and provoking sudden bugs in the future.


Maybe that would really be better made as an syntactic sugar - implicit
conversion lib for some carefully selected subclasses of Seq, but not the
Seq in general.
Or some trait, that only can be applied to "some carefully selected
subclasses of Seq", if this is possible.


--
Написано в почтовом клиенте браузера Opera: http://www.opera.com/mail/

Reply all
Reply to author
Forward
0 new messages