for loops

27 views
Skip to first unread message

Ivo Kasiuk

unread,
Dec 9, 2011, 12:05:49 PM12/9/11
to ceylon-dev
Hi,

I noticed that in my Ceylon code most "for" loops that use the x..y
notation look like this:

if (count > 0) {
for (i in start..(start+count-1).natural) {
// ...
}
}

Not very nice. Is there a better way to write this?

Ivo

Gavin King

unread,
Dec 9, 2011, 12:20:02 PM12/9/11
to ceylo...@googlegroups.com
Well, to iterate over Naturals, the best you can do right now is:

if (count > 0) {
for (i in start..start+count.predecessor) {
// ...
}
}

But I've been toying with adding some kind of object to produce ranges
by start and length (instead of from/to). Then the idiom would be:

for (i in segment(start,count)) {
// ...
}

We've even floated the idea of providing an operator for this, something like:

for (i in start..<count) {
// ...
}

But I'm not really warming to that idea. I've seen it in other
languages and never really loved it.

But anyway, my guess is that you're really trying to iterate over a
segment of a sequence or string or something like that, in which case
iterating over indexes is simply never the correct idiom in Ceylon.
The correct approach is always this:

for (x in sequence.segment(start,count)) {
// ...
}

Basically, we almost *never* iterate over indexes in Ceylon, which is
why Ceylon doesn't support a C-style "for".

--
Gavin King
gavin...@gmail.com
http://in.relation.to/Bloggers/Gavin
http://hibernate.org
http://seamframework.org

Gavin King

unread,
Dec 9, 2011, 12:26:55 PM12/9/11
to ceylo...@googlegroups.com
Note that we're developing this convention of supporting both "spans"
which are defined by a starting and ending index, and "segments" which
are defined by a starting index and a length. Ranges are
"pascal-style" spans, not "C-style" segments, which is why they're not
convenient for the usecase you raise.

Tako Schotanus

unread,
Dec 9, 2011, 12:39:45 PM12/9/11
to ceylo...@googlegroups.com
Personally I could still live with  start..(start+count-1) but it's the .natural that makes it ugly to me, it seems one of those places where the compiler should just be able to "understand" you.

-Tako

Gavin King

unread,
Dec 9, 2011, 12:54:00 PM12/9/11
to ceylo...@googlegroups.com
Well, I suppose we could make the Castable trick we use for arithmetic
operators work also for the ".." operator.

But the bigger question is: is it really the right thing to say that
natural-natural results in an Integer?

On Fri, Dec 9, 2011 at 11:39 AM, Tako Schotanus <ta...@codejive.org> wrote:
> Personally I could still live with  start..(start+count-1) but it's the
> .natural that makes it ugly to me, it seems one of those places where the
> compiler should just be able to "understand" you.

Ross Tate

unread,
Dec 9, 2011, 1:25:37 PM12/9/11
to ceylo...@googlegroups.com
The fundamental problem is that "x < y" is not equivalent to "x <= y-1" with naturals or with modular integers. It's a good thing that the example checks that count is nonzero, cuz otherwise there'd be a bug when start and count are both 0. I imagine others will not be so fortunate.

Also, why's there a ".natural"? Does that round up, or does it do absolute value?

Gavin King

unread,
Dec 9, 2011, 1:39:19 PM12/9/11
to ceylo...@googlegroups.com
On Fri, Dec 9, 2011 at 12:25 PM, Ross Tate <rt...@cs.ucsd.edu> wrote:

> Also, why's there a ".natural"? Does that round up, or does it do absolute
> value?

I recently decided that "x-y" should result in an Integer when x and y
are both Natural, rather than resulting in an exception when x<y. This
is not a decision I'm especially confident about, but it seems more
in line with our whole philosophy. So you need to explicitly call
(x-y).natural if you want to cast the Integer back down to a Natural,
risking an exception.

Ivo Kasiuk

unread,
Dec 9, 2011, 3:36:29 PM12/9/11
to ceylon-dev
On 9 Dez., 19:25, Ross Tate <rt...@cs.ucsd.edu> wrote:
> The fundamental problem is that "x < y" is not equivalent to "x <= y-1"
> with naturals or with modular integers. It's a good thing that the example
> checks that count is nonzero, cuz otherwise there'd be a bug when start and
> count are both 0. I imagine others will not be so fortunate.

That's exactly the point.
The real problem with "for (i in x..y)" is: it is potentially
dangerous.

1. When the bounds are not literals, it is easy to come to the
impression that it will also work for the case where you would have 0
iterations. But actually, x..y can never result in less than one
iteration, because in the case where you might expect 0 iterations,
you really get 2 iterations in the other direction.

2. If x is 0, you get the problem described by Ross.

3. When writing the program, the author almost always knows in which
direction the loop is supposed to iterate. So it is not helpful but
actually harmful that the direction of iteration can automatically
turn around if y happens to be less than x.

Ok, you can probably avoid these problems by using something like
segments. But as long as "x..y" is the most natural syntax available
for iterating over a range of numbers, many people will use it for
that purpose - even in cases where a different (but less obvious)
construct would possible have been better. Something so seemingly
obvious should just not backfire that easily!


Concerning x-y potentially throwing an exception vs. x-y resulting in
an Integer (and num.natural potentially throwing an exception):
The latter is clearly better IMO, because otherwise a-b+c and a+c-b
are not equivalent anymore, which seems rather counter-intuitive.

Stephane Epardaud

unread,
Dec 9, 2011, 4:24:00 PM12/9/11
to ceylo...@googlegroups.com
On 9 December 2011 19:39, Gavin King <gavin...@gmail.com> wrote:

I recently decided that "x-y" should result in an Integer when x and y
are both Natural, rather than resulting in an exception when x<y. This
is not a decision I'm especially confident about, but  it seems more
in line with our whole philosophy. So you need to explicitly call
(x-y).natural if you want to cast the Integer back down to a Natural,
risking an exception.
 
I'm not a big fan of that one, I think it's trying to solve the unsolvable. It doesn't feel right to not be able to subtract natural numbers, it's very counter-intuitive. Plus if we go that way why not make + return a BigDecimal since it may overflow?
The other problem I have with Natural is that as a C user I tend to expect to get an "unsigned int" which goes to twice the maximum of Integer, since it's not signed, but that is not the case :(
I would much rather get an exception when a Natural goes below zero, in the same way that I'd much rather get an exception when it overflows rather than a stupid flag in a CPU register and a "funny" effect of getting a negative number when we count too high.
-- 
Stéphane Épardaud

Gavin King

unread,
Dec 9, 2011, 4:52:26 PM12/9/11
to ceylo...@googlegroups.com
On Fri, Dec 9, 2011 at 3:24 PM, Stephane Epardaud
<stephane...@gmail.com> wrote:

> I'm not a big fan of that one, I think it's trying to solve the unsolvable.

So then the bigger question is: is it even worth having Natural? Or
should we just eliminate it?

To me it serves a really nice *documentation* purpose in the very
common case when a parameter only accepts positive numbers and zero.
But if its just going to get in people's way and annoy them, without
adding any real extra typesafety, I can live without it.

Tako Schotanus

unread,
Dec 9, 2011, 5:11:15 PM12/9/11
to ceylo...@googlegroups.com
On Fri, Dec 9, 2011 at 22:52, Gavin King <gavin...@gmail.com> wrote:
On Fri, Dec 9, 2011 at 3:24 PM, Stephane Epardaud
<stephane...@gmail.com> wrote:

> I'm not a big fan of that one, I think it's trying to solve the unsolvable.

So then the bigger question is: is it even worth having Natural? Or
should we just eliminate it?

To me it serves a really nice *documentation* purpose in the very
common case when a parameter only accepts positive numbers and zero.
But if its just going to get in people's way and annoy them, without
adding any real extra typesafety, I can live without it.


Personally it has always annoyed me, beginning with having to write +2, +3 etc instead of plain 2 and 3.
But I could live with that if at least the conversions from one to the other would be automatic in most cases (using a Natural in case of an Integer should always be possible for example)

-Tako

Gavin King

unread,
Dec 9, 2011, 5:17:57 PM12/9/11
to ceylo...@googlegroups.com
On Fri, Dec 9, 2011 at 3:52 PM, Gavin King <gavin...@gmail.com> wrote:
> On Fri, Dec 9, 2011 at 3:24 PM, Stephane Epardaud
> <stephane...@gmail.com> wrote:
>
>> I'm not a big fan of that one, I think it's trying to solve the unsolvable.
>
> So then the bigger question is: is it even worth having Natural? Or
> should we just eliminate it?
>
> To me it serves a really nice *documentation* purpose in the very
> common case when a parameter only accepts positive numbers and zero.
> But if its just going to get in people's way and annoy them, without
> adding any real extra typesafety, I can live without it.

There are several downsides of Natural:

* the need to prefix integer literals with + as in +10
* the simple complexity of having 5 numeric types instead of 4
* the annoyance that Natural is not automatically widened to
Integer in invocations and return statements
* the fact that it's unfamiliar and potentially intimidating and will likely
cause problems and confusion for beginners
* it slightly complexifies Java interop

That's not a short list.

I'm worried that all the accumulated unfamiliarity of Foo vs Foo?,
Empty vs Sequence<Foo> and Integer vs Natural is going to be
just a little too much for people taking their first steps with this
language.

Gavin King

unread,
Dec 9, 2011, 5:19:29 PM12/9/11
to ceylo...@googlegroups.com
On Fri, Dec 9, 2011 at 4:11 PM, Tako Schotanus <ta...@codejive.org> wrote:

> Personally it has always annoyed me, beginning with having to write +2, +3
> etc instead of plain 2 and 3.
> But I could live with that if at least the conversions from one to the other
> would be automatic in most cases (using a Natural in case of an Integer
> should always be possible for example)

Right. We're trying to create a language that is *less* irritating that Java.

Ivo Kasiuk

unread,
Dec 9, 2011, 5:38:26 PM12/9/11
to ceylo...@googlegroups.com
When I first learned Java, the fact that it did not have unsigned numeric types immediately made sense to me. In C++, when working with both signed and unsigned values, it had always seemed almost impossible to get it completely right - in any case, it required a lot of thinking. Java neatly eliminated all this trouble with this straight-forward simplification.

Gavin King

unread,
Dec 9, 2011, 5:43:37 PM12/9/11
to ceylo...@googlegroups.com
On the other hand, lack of unsigned types is a *really* common
complaint about Java. One I've heard many times. Though I think the
people who want them are interested in them mainly for doing
bitshifting and stuff like that.

Tako Schotanus

unread,
Dec 9, 2011, 6:08:46 PM12/9/11
to ceylo...@googlegroups.com
Exactly, and bit twiddling can be handled by special libraries if really needed.

-Tako

Ivo Kasiuk

unread,
Dec 9, 2011, 6:09:53 PM12/9/11
to ceylo...@googlegroups.com
Well, I have done a *lot* of "bitshifting and stuff like that" in Java, and it did work. Ok, sometimes I have been among those people who complained about the lack of unsigned types, but all things considered, it has probably made life easier.
I do like the Natural type: it feels much cleaner in situations where negative values make no sense. But somehow that's more an emotional argument than a pragmatic one. Is the list of benefits of Natural really long enough to justify its existence?

Gavin King

unread,
Dec 9, 2011, 6:17:52 PM12/9/11
to ceylo...@googlegroups.com
On Fri, Dec 9, 2011 at 5:09 PM, Ivo Kasiuk <i.ka...@googlemail.com> wrote:

> Is the list of benefits of Natural really long enough to justify its existence?

At this point it's pretty hard to know for sure, but it's beginning to
feel like the answer is "no".

It would not bother me so much if it weren't for the fact that
sequences pretty much force you into using Natural for certain basic
things.

Ross Tate

unread,
Dec 9, 2011, 6:25:24 PM12/9/11
to ceylo...@googlegroups.com
I'd like to look into some stuff for dealing with numbers. Another problem is varying degrees of precision. And another problem is approximation and overflow and optimization. So, I'd say let's put this on held for a bit.

Oh, yeah, and my ".natural" question was more about the fact that the conversion is ambiguous: does it mean round up (i.e. negatives become 0) or does it mean absolute value or does it mean reinterpret modulo (i.e. -1 becomes 255).

Gavin King

unread,
Dec 9, 2011, 6:30:22 PM12/9/11
to ceylo...@googlegroups.com
On Fri, Dec 9, 2011 at 5:25 PM, Ross Tate <rt...@cs.ucsd.edu> wrote:
> I'd like to look into some stuff for dealing with numbers. Another problem
> is varying degrees of precision. And another problem is approximation and
> overflow and optimization. So, I'd say let's put this on held for a bit.

There's little we can do with respect to this kind of stuff if we want
high performance on the JVM. Because at the end these things get
optimized down to a Java long or double.

Ross Tate

unread,
Dec 9, 2011, 7:54:42 PM12/9/11
to ceylo...@googlegroups.com
There are still options. For example, an optimization you can't usually do is say "x < x + 1" is equivalent to true, because this isn't true for modular integers. But if you have a type which represents integers *approximated by* 32-bit modular integers, then such an optimization is allowed because it more accurately approximates the integers. You can also implement such a type using 64-bit modular integers or whatever fits the machine best so long as its more precise than 32-bit modular integers. This also lets you get around IEEE754 peculiarities, where "x*y - y*x" is not equivalent to 0, by having a type that represents real numbers approximated by 32- or 64-bit floating point numbers. Of course, you can also provide a type that represent precisely 32-bit modular integers or 32-bit IEEE754 so that programmers can communicate when their code is relying on the exact behaviors of these implementations.

So, there are options, but it's important that the language doesn't become burdened with options (such as Natural). Hence, I'd like to investigate to see if there's a clean solution.

Gavin King

unread,
Dec 9, 2011, 9:49:54 PM12/9/11
to ceylo...@googlegroups.com
Frankly I'm ready to go ahead and remove Natural right now if that is
the consensus of the other folks here. It doesn't really add very much
in terms of type safety - a little, perhaps - and what it does add is
probably outweighed by the inconvenience and plain
getting-in-the-wayness of having to convert between integers and
naturals. I don't want to have a thing like this of marginal value
that is a turnoff for beginners, and continues to be minor irritation
to those who are accustomed to it, if we can help it. I mean, if we
remove it now, I doubt that anybody will every really miss it, and I'm
sure we will avoid lots and lots of complaints.

--

Tako Schotanus

unread,
Dec 9, 2011, 9:57:46 PM12/9/11
to ceylo...@googlegroups.com
I'd say go ahead, I will not miss it.

-Tako

Gavin King

unread,
Dec 9, 2011, 10:03:26 PM12/9/11
to ceylo...@googlegroups.com
On Fri, Dec 9, 2011 at 2:36 PM, Ivo Kasiuk <i.ka...@googlemail.com> wrote:

> Ok, you can probably avoid these problems by using something like
> segments. But as long as "x..y" is the most natural syntax available
> for iterating over a range of numbers, many people will use it for
> that purpose - even in cases where a different (but less obvious)
> construct would possible have been better. Something so seemingly
> obvious should just not backfire that easily!

So in my experience there are two common cases:

1. you are given a startpoint and endpoint and you want to iterate
over them inclusively, or
2. you are given a startpoint and a length, and you want exactly
length elements.

I'm calling 1 a "span" and 2 a "segment", though perhaps someone has
seen better terminology for these things.

Common C idiom confuses the two usecases and we wind up with a
"djikstra span" where you are given a startpoint and an endpoint and
want everything between then including the startpoint and excluding
the endpoint. The thing we write as [x,y) in mathematics. To my mind,
a "djikstra span" is a far less naturally occurring thing than either
1 or 2 above, and that's where you're getting your "start+length-1"
mess from. Now, this idiom is quite deeply ingrained in the C family
of languages, but I don't think it's actually the most convenient
thing in most usecases.

Now, one idea I've been toying with since reading you're email is
using : as the operator for creating a segment of ordinal values. I
don't know why, but I find that the following reads really naturally
to me:

for (i in 0:length) { ... }

I don't know if this appeals to anyone else...

Gavin King

unread,
Dec 9, 2011, 10:05:17 PM12/9/11
to ceylo...@googlegroups.com
On Fri, Dec 9, 2011 at 9:03 PM, Gavin King <gavin...@gmail.com> wrote:

> Now, one idea I've been toying with since reading you're email is
> using : as the operator for creating a segment of ordinal values. I
> don't know why, but I find that the following reads really naturally
> to me:
>
>    for (i in 0:length) { ... }
>
> I don't know if this appeals to anyone else...

And of course

sequence[0:n]

would give you the first n elements of a sequence.

sequence[sequence.lastIndex:-n]

might give you the last n elements.

Gavin King

unread,
Dec 9, 2011, 10:10:25 PM12/9/11
to ceylo...@googlegroups.com
On Fri, Dec 9, 2011 at 9:03 PM, Gavin King <gavin...@gmail.com> wrote:

> Now, one idea I've been toying with since reading you're email is

"your email" dammit!

Gavin King

unread,
Dec 10, 2011, 12:01:20 AM12/10/11
to ceylo...@googlegroups.com
So I just created branches named "sin_natural" of the typechecker and
language module projects. With very little work, I've got all tests
passing. So you guys can have a play. I don't think it would be much
work to make the same change to the compiler project.

Ivo Kasiuk

unread,
Dec 10, 2011, 4:12:11 AM12/10/11
to ceylo...@googlegroups.com

Cool! I like it. And it has the advantage that people don't end up using x..y just because it looks more simple.

The distinction between span and segment is clear and logical. But the syntax should not create an unjustified preference for segments over spans. Having syntactic sugar for both solves that problem.

Ivo Kasiuk

unread,
Dec 10, 2011, 4:27:38 AM12/10/11
to ceylo...@googlegroups.com

Not sure about this myself, just trying to figure out if there could be a compromise:

What about removing Natual as a separate data type and instead introducing an annotated integer like "natural Integer" (and possibly even "Natural" as a shortcut for that). The compiler would then insert a check for x>=0 every time a value is stored in a "natural Integer". So you could write something like:

natural Integer x = a - b * (c - 1);

And even if a, b and c are also all "natural Integer", the only point where a check would be performed is when assigning the result of the calculation to x.

Of course, no check would be necessary when assigning one "natural Integer" to another. So you would basically have a "light" version of the old Natural.

Gavin King

unread,
Dec 10, 2011, 5:25:36 AM12/10/11
to ceylo...@googlegroups.com
Yeah, something like this is totally possible, but the thing is it
doesn't even need to be in the language module. Once we have support
for interceptors (M5 according to the current roadmap) purely runtime
checks like this can be done in a library without explicit compiler
support.

Ivo Kasiuk

unread,
Dec 10, 2011, 5:39:37 AM12/10/11
to ceylo...@googlegroups.com

True. But I'm not sure if interceptors would be a practicable solution here. The tricky part would not be to insert the checks but to figure out when *not* to insert a check. If even a simple x++ results in a check for x>=0, possibly even through an additional method call, the whole concept becomes so inefficient that it would be rendered useless.

Gavin King

unread,
Dec 10, 2011, 5:46:04 AM12/10/11
to ceylo...@googlegroups.com
On Fri, Dec 9, 2011 at 9:03 PM, Gavin King <gavin...@gmail.com> wrote:

>    for (i in 0:length) { ... }

Another potential syntax could be:

for (i in 0../length) { ... }

sequence[0../n]

However at this point we start getting into cryptic ascii art territory.

Gavin King

unread,
Dec 10, 2011, 5:48:16 AM12/10/11
to ceylo...@googlegroups.com
On Sat, Dec 10, 2011 at 4:39 AM, Ivo Kasiuk <i.ka...@googlemail.com> wrote:
> True. But I'm not sure if interceptors would be a practicable solution here.
> The tricky part would not be to insert the checks but to figure out when
> *not* to insert a check. If even a simple x++ results in a check for x>=0,
> possibly even through an additional method call, the whole concept becomes
> so inefficient that it would be rendered useless.

Perhaps you're right, but there might be a way to make it work out.

Gavin King

unread,
Dec 10, 2011, 12:05:17 PM12/10/11
to ceylo...@googlegroups.com
https://github.com/ceylon/ceylon-spec/issues/98

On Fri, Dec 9, 2011 at 8:57 PM, Tako Schotanus <ta...@codejive.org> wrote:
> I'd say go ahead, I will not miss it.
>
> -Tako
>
>
>
> On Sat, Dec 10, 2011 at 03:49, Gavin King <gavin...@gmail.com> wrote:
>>
>> Frankly I'm ready to go ahead and remove Natural right now if that is
>> the consensus of the other folks here. It doesn't really add very much
>> in terms of type safety - a little, perhaps - and what it does add is
>> probably outweighed by the inconvenience and plain
>> getting-in-the-wayness of having to convert between integers and
>> naturals. I don't want to have a thing like this of marginal value
>> that is a turnoff for beginners, and continues to be minor irritation
>> to those who are accustomed to it, if we can help it. I mean, if we
>> remove it now, I doubt that anybody will every really miss it, and I'm
>> sure we will avoid lots and lots of complaints.

Gavin King

unread,
Dec 11, 2011, 11:46:01 AM12/11/11
to ceylo...@googlegroups.com
On Sat, Dec 10, 2011 at 3:12 AM, Ivo Kasiuk <i.ka...@googlemail.com> wrote:
> Cool! I like it. And it has the advantage that people don't end up using
> x..y just because it looks more simple.
>
> The distinction between span and segment is clear and logical. But the
> syntax should not create an unjustified preference for segments over spans.
> Having syntactic sugar for both solves that problem.

https://github.com/ceylon/ceylon-spec/issues/99

http://ceylon-lang.org
http://hibernate.org
http://seamframework.org

Emmanuel Bernard

unread,
Dec 12, 2011, 4:54:45 AM12/12/11
to ceylo...@googlegroups.com

On 9 déc. 2011, at 22:52, Gavin King wrote:
>
> To me it serves a really nice *documentation* purpose in the very
> common case when a parameter only accepts positive numbers and zero.
> But if its just going to get in people's way and annoy them, without
> adding any real extra typesafety, I can live without it.

I'd rather have a constraint annotation for this "documentation" purpose.

Reply all
Reply to author
Forward
0 new messages