Exception handling vs. hasNext()

# Garrett Smith (17 years ago)

Which is better?

var nodes : int; var widgetMap = Widget.instances; // a map. var it:Iterator<string> = widgetMap.getKeys();

-- this: --

try { widgetMap.get(it.next()).hide(); } catch(Exception e) { if(e instanceof StopIteration) {

} }

-- or this: --

while(it.hasNext()) { widgetMap.get(it.next()).hide();; }

It might be the case that there might be some error I want to catch other than StopIteration. In that case, to be backwards-compatible and work across implementations, the developer must use conditional checks inside 1 catch block.

A hasNext() would not prevent the developer from writing such code. Omitting hasNext() forces developers to use exception handling for non-exceptional condition.

How does using try/catch for normal termination of the loop look?

It looks like all exceptions are unchecked in ES4. correct? (I cannot verify this to be true because I am unable to access the spec namespace on ecmascript.org.)

# Erik Arvidsson (17 years ago)

One benefit of StopIteration is that code inside map/some/every etc can just throw a StopIteration to stop the iteration. The same thing is harder to keep clean with a hasNext/next pattern.

# Garrett Smith (17 years ago)

Can you explain a little better by showing a code example?

Thanks.

On Nov 16, 2007 5:44 PM, Erik Arvidsson <erik.arvidsson at gmail.com> wrote:

One benefit of StopIteration is that code inside map/some/every etc can just throw a StopIteration to stop the iteration. The same thing is harder to keep clean with a hasNext/next pattern.

On Nov 16, 2007 5:30 PM, Garrett Smith <dhtmlkitchen at gmail.com> wrote:

Which is better?

var nodes : int; var widgetMap = Widget.instances; // a map. var it:Iterator<string> = widgetMap.getKeys();

-- this: --

try { widgetMap.get(it.next()).hide(); } catch(Exception e) { if(e instanceof StopIteration) {

} }

-- or this: --

while(it.hasNext()) { widgetMap.get(it.next()).hide();; }

It might be the case that there might be some error I want to catch other than StopIteration. In that case, to be backwards-compatible and work across implementations, the developer must use conditional checks inside 1 catch block.

A hasNext() would not prevent the developer from writing such code. Omitting hasNext() forces developers to use exception handling for non-exceptional condition.

How does using try/catch for normal termination of the loop look?

It looks like all exceptions are unchecked in ES4. correct? (I cannot verify this to be true because I am unable to access the spec namespace on ecmascript.org.)


Es4-discuss mailing list Es4-discuss at mozilla.org, mail.mozilla.org/listinfo/es4-discuss

-- erik

-- Programming is a collaborative art.

# Kris Kowal (17 years ago)

Consider the iteration decorator pattern:

var i = 0; var producer = { 'next': function () { if (i < 10) { return i++; } else { throw new StopIteration(); } } };

var consumer = { 'next': function () { return producer.next() * 2; }, };

while (true) { try { log(consumer.next(); } catch (exception) { if (exception instanceof StopIteration) { } else { throw exception; } } }

(or, hiding the details, conceptually the equivalent:)

var producer = range(10); var consumer = new Iteration(function () { return producer.next() * 2; }); consumer.forEach(log);

Both of these examples would log even numbers in the interval [0, 20). The consumer iteration does not have a terminal condition, but the producer iteration's StopIteration exception passed up through the decorator to the consumer. This would work really well if you recursively decorated or "pipelined" an iteration with a conenience function like eachIter.

/* an indefinite iteration of the values in someArray that are at indicies that are multiples of six */ var it = range(100).eachIter(function (n) n * 2).whereIter(function (n) !(n % 3)).eachIter(function (i) someArray[i]).each(log)

In that particular example, the last each on the line would catch the StopIteration thrown by Range.next when it reaches 100. Naturally, no 100 item arrays are ever created.

Kris Kowal

# Kris Kowal (17 years ago)

/* an indefinite iteration of the values in someArray that are at indicies that are multiples of six */ var it = range(100).eachIter(function (n) n * 2).whereIter(function (n) !(n % 3)).eachIter(function (i) someArray[i]).each(log)

In that particular example, the last each on the line would catch the StopIteration thrown by Range.next when it reaches 100. Naturally, no 100 item arrays are ever created.

Kris Kowal

Sorry, that last |each| should be |forEach| for my claim that no 100 item arrays would be created to be accurate.

Kris Kowal

# Yuh-Ruey Chen (17 years ago)

More advantages to StopIteration:

  1. There are some iterations in which calculating whether the next iteration exists is non-trivial, such as iterating on trees.

  2. While one could work around non-trivial hasNext() calculations by "pre-advancing" the iterator, i.e. if an iterator has returned the i-th item, it has already calculated the (i+1)-th item, making hasNext() simply check if the (i+1)-th item exists, such an approach is not only less efficient, but won't work if the iterator makes side effects.

  3. Repeated hasNext() checks can be slower than throwing and catching a StopIteration, especially if the compiler is optimized to expect that StopIteration from an iterator.

  4. Iterator-generators hide the messy business of StopIteration:

// range is an iterator-generator (note the yield statement) function range(len) { for (let i = 0; i < len; ++i) yield i; }

for (let x in range(10)) print(x);

No mention of StopIteration - or even next() - anywhere in that code yet |range(10)| is clearly an iterator.

In case you were wondering, ES4's iteration protocol is heavily inspired from Python, and I think Python has handled this iteration business very well.

-Yuh-Ruey Chen

# Brendan Eich (17 years ago)

On Nov 16, 2007, at 5:30 PM, Garrett Smith wrote:

Which is better?

var nodes : int; var widgetMap = Widget.instances; // a map. var it:Iterator<string> = widgetMap.getKeys();

-- this: --

try { widgetMap.get(it.next()).hide(); } catch(Exception e) { if(e instanceof StopIteration) {

} }

-- or this: --

while(it.hasNext()) { widgetMap.get(it.next()).hide();; }

Neither. This is best:

for each (w in widgetMap) w.hide();

But your two examples are not equivalent. The first calls the
iterator exactly once, the second loops over all keys. I'm asuming
widgetMap.get(key) returns the corresponding widget value, so for- each-in is the way to loop, not for-in (and never while).

As in Python, you rarely have to get or make an iterator explicitly;
you almost never have to catch StopIteration.

# Garrett Smith (17 years ago)

On Nov 17, 2007 5:50 AM, Brendan Eich <brendan at mozilla.org> wrote:

On Nov 16, 2007, at 5:30 PM, Garrett Smith wrote:

Which is better?

var nodes : int; var widgetMap = Widget.instances; // a map. var it:Iterator<string> = widgetMap.getKeys();

-- this: --

try { widgetMap.get(it.next()).hide(); } catch(Exception e) { if(e instanceof StopIteration) {

} }

-- or this: --

while(it.hasNext()) { widgetMap.get(it.next()).hide();; }

Neither. This is best:

for each (w in widgetMap) w.hide();

But your two examples are not equivalent. The first calls the iterator exactly once, the second loops over all keys. I'm asuming widgetMap.get(key) returns the corresponding widget value, so for- each-in is the way to loop, not for-in (and never while).

Yep, I typed that up @ wk. My brain was still holding some PHP code and other irrelevant things (my dislike for PHP, food, wanting to lift, and other topics that may be less relevant).

So... I want to iterate over the keys in a Map.

I would not like to iterate over properties of the map (and it's prototype); so I'm pretty sure that for in (or for each) is not what I want. I want to iterate over the key (not object properties).

Kris, it looks like in your example, there's no option for handling the loop normally:

while (true) { try { log(consumer.next(); } catch (exception) { if (exception instanceof StopIteration) { } else { throw exception; } } }

I see how normal termination is handled via a try catch, with a conditional if clause in the catch. What I fail to see is why this is better than hasNext/next paradigm.

It looks like the reasons for omitting hasNext are:

  1. Python does it
  2. using try/catch would be faster
  3. harder to keep clean with next/hasNext pattern. Reason 1 isn't a reason for omitting hasNext, is it? Reason 2 is just copying Python's poor reasoning. Reason 3 has not yet been shown to be true in any way.

I thought the purpose of exception handling was to handle exceptional conditions.

Does the new Iterator is requires the use of try/catch in a way that is appropriate?

If so, why?

In ES3, I use hasOwnProperty in a for/in loop over an object's properties. Not the prettiest thing, but at least it requires no exception handling.

How is it possible to iterate over the keys in a Map? I'd like to avoid using try/catch, unless something in the loop body might require it, and in that case, I'll want to be very clear on what might throw an exception, how, and why, as well as provide correct handling of that exception. Is there a way to get a maps keys as an Array?

I've seen APIs of popular JS libraries swallow exceptions. For example, YUI's Connection Manager used to do this on the callback handler, silently swallowing any error that I, as a user of that API, might throw (painful). I'm wondering if providing an API that requires try/catch for non-exceptional conditions cheapens the nature of exception handling. I mean, won't developers say "oh, it's just a normal exception, we can ignore it."

Garrett

# Yuh-Ruey Chen (17 years ago)

Garrett Smith wrote:

How is it possible to iterate over the keys in a Map? I'd like to avoid using try/catch, unless something in the loop body might require it, and in that case, I'll want to be very clear on what might throw an exception, how, and why, as well as provide correct handling of that exception. Is there a way to get a maps keys as an Array?

I think you're misunderstanding how iterators work. You don't need to explicitly use the next() method or catch StopIteration. Please look at how Python does it. There are plenty of examples on the web; here's one:

for x in range(10): print(x)

which is practically equivalent in ES3 to:

for (let x = 0; x < 10; ++x) print(x);

The Python for-in is equivalent in ES4 (assuming range is defined equivalently) to:

for (let x in range(10)) print(x);

This practically translates to:

let $iter = range(10); // $iter not visible to rest of code try { for (;;) { let x = $iter.next(); print(x); } } catch (e: StopException) {}

As you can see, the for-in loop syntax completely hides the next() and StopIteration. It's definitely not as cumbersome as you think - in fact, it's even easier to use than hasNext()/next()-style iterators.

To answer your specific question, Map should have a getKeys() method (according to the wiki) that returns an iterator iterating over the keys of the map:

for (let k in map.getKeys()) print(k);

FYI, the original ES3 for-in is actual a special case of the ES4 for-in. When you do |for (let p in obj)|, it's actually iterating over obj's intrinsic iterator (obj.iterator::get() according to the wiki), which enumerates obj's properties, just like what it does in ES3.

-Yuh-Ruey Chen

# Brendan Eich (17 years ago)

On Nov 19, 2007, at 7:51 AM, Garrett Smith wrote:

So... I want to iterate over the keys in a Map.

Yuh-Ruey already replied, but I just wanted to point out that this:

for (let k in map.iterator::getKeys()) print(k);

can be done simply via:

for (let k in map) print(k)

because the Map class provides the key iterator that its
iterator::getKeys method returns from its default iterator::get
method (which for-in calls) too. That is, by default, Map iteration
returns keys.

If you want values or items (key/value pairs), you can get those too.
To iterate over values, you might use:

for (let v in map.iterator::getValues()) print(v);

but you should rather use the more succinct for-each-in loop:

for each (let v in map) print(v);

To iterate over items, use:

for (let [k, v] in map.iterator::getItems()) print(k, v);

This item iteration case has no more concise form analogous to for- each-in, because if ES4 were always to map:

for (let [k, v] in map) ...

to:

for (let [k, v] in map.iterator::getItems()) ...

it would wrongly preempt destructuring of arbitrary properties from
the value returned by the default iterator (the object returned by
map.iterator::get()) -- it would prevent writing:

for (let [s, v, o] in tripledb) ...

where tripledb.iterator::get() returns an iterator over [subject,
verb, object] triples.

If you feel the need to use a Java-like hasMore/getNext pattern, just
lie down till the feeling goes away ;-). The Pythonic iteration
protocol is simpler to use, more efficient to implement, and has no
unchecked inconsistent state possibilities between hasMore and
getNext. And yes, leveraging Python here is good reuse of language
design, implementation experience, and user knowledge.

You do not need to catch StopIteration in any common cases. So don't
fret about the fact that there's an exception thrown under the hood.
Exceptions are not all errors.

# P T Withington (17 years ago)

On 2007-11-19, at 12:40 EST, Brendan Eich wrote:

This item iteration case has no more concise form analogous to for- each-in, because if ES4 were always to map:

for (let [k, v] in map) ...

to:

for (let [k, v] in map.iterator::getItems()) ...

it would wrongly preempt destructuring of arbitrary properties from the value returned by the default iterator

Say, if we don't waste parens on union types, what's wrong with:

for (let (k, v) in map) ...

? Just a thought.

# Brendan Eich (17 years ago)

On Nov 19, 2007, at 6:45 PM, P T Withington wrote:

On 2007-11-19, at 12:40 EST, Brendan Eich wrote:

This item iteration case has no more concise form analogous to for- each-in, because if ES4 were always to map:

for (let [k, v] in map) ...

to:

for (let [k, v] in map.iterator::getItems()) ...

it would wrongly preempt destructuring of arbitrary properties from the value returned by the default iterator

Say, if we don't waste parens on union types, what's wrong with:

for (let (k, v) in map) ...

Union types are irrelevant in value expressions anyway, but group
assignment was rejected in favor of destructuring (based on Opera
precedent). See

proposals:group_assignment

Consider that the implementation of a "group" or "tuple" iterator
will return [k, v] or [s, v, o]. The array destructuring syntax
mimicks array initialiser syntax. Same goes for object destructuring,
with a convenient shorthand:

let {p: a, q: b} = o // bind a = o.p and b = o.q

When you want the bound names to be the same as the destructured
property names, use the shorthand:

let {p, q} = o

Since we're not adding tuples as a distinct type expressed using
parentheses, the let [k, v] form remains.