If you're not familiar with the shortcomings of for-in in JS already,
I've compiled a short list here:
1. for (x in array) enumerates the indexes, NOT the objects.
Additionally, the indexes are strings not numbers (doing x + 1 when x
is 0 will give you "01" not "1").
2. Lots of gotchas with prototype added properties: you have to use
hasOwnProperty whenever you use for-in, if not you'll for example get
"isa" when iterating over an array.
3. Not extensible: If you have a custom collection class, like a
linked list or a set or a tree OR anything, you can't make it work
with for-in. Basically, for-in is reserved for iterating string
properties of an object and thats it.
4. forEach is a slow alternative: each iteration is a function call,
each of which can potentially form a closure, this can lead to simple
iteration being orders of magnitude slower.
5. forEach can be a confusing alternative (having to pass in the
"this" variable, etc.)
However, we have the ability to fix all this with @for :
@for(object in array)
{
//blah
}
The reason behind using @for and not simply overriding the normal for
keyword is to have a way to use the default JS behavior. The idea is
that any collection class that implements -
countByEnumeratingWithState:objects:count: will work with @for. That
means @for(object in set) and @for(object in dictionary) will work and
no more needing to use enumerators. These will also actually be faster
than bother enumerator code AND forEach since they will require no
function calls.
That being said, we've also been playing around with some additional
ideas to make @for even more useful. First off, having multiple vars
like Ruby does, to allow for things like:
@for(object, index in array)
@for(object, key in dictionary)
I think this is a good idea, the only down side being the somewhat
unintuitive ordering.
The other idea we've been playing around with is adding "modifiers" to
better convey how you want to iterate. Right now, for-in is intended
to be used in an "unordered" fashion, with no real guarantee of the
order in which the objects are visited. With arrays there is the
implicit expectation that its 0 -> length-1. Going in reverse is thus
difficult and requires you to reverse the array or do math:
[array reverse].forEach(...)
modifiers would work as such:
@for reversed (object in array) { statements; }
These modifiers would be passed to the API, so you can imagine adding
*any* obscure behavior you wanted, such as:
@for even (object in array) { statements; }
@for odd (object in array) { statements; }
@for odd reversed (object in array) { statements; }
@for sorted (object in array) { statements; }
etc. Whatever the collection implements. This starts to make much more
sense with non-traditional collections like trees. With trees, @for is
very difficult since it is *very* common that algorithms require you
to visit nodes in a particular order: breadth first, depth first, in-
order, etc. Without modifiers, you would basically have no choice but
to always traverse the tree manually. However, this would be trivial
with modifiers:
@for depth-first (node in tree) { statements; }
Trees are apparent everywhere in Cappuccino, not just when using
CPTreeNode. So, let's say you wanted to set a property on every view
in a window. Right now, you have to use recursion or do some tricky
stack programming, but with @for you could just do:
@for breadth-first (view in [theWindow contentView]) { [view
setWhatever:5]; }
The main problem with this right now is that the syntax doesn't allow
for doing things like passing in a dynamic modifier:
var modifier = "breadth-first";
@for modifier (view in contentView)...
That will of course not work since "modifier" will be used as a
modifier. The alternative is to add more syntax:
@for each("breadth-first", customModifier) (view in contentView) { }
More verbose but more flexible
@for <"breadth-first", customModifier> (view in contentView) { } A
little less verbose.
So those are our ideas so far. Please feel free to chime in with any
thoughts/additions/etc. If you have ideas on alternative syntax or
anything let us know, once we reach consensus we can start
implementing!
@for(index => object in array)
or more in line with JS sytax:
@for(key:object in dictionary)
or
@for({key:object} as pair in dictionary) {
// CPLog(@"key = %@, obj = %@", pair.key, pair.object);
}
Just thinking out loud.
On 30 Dic, 08:30, Francisco Tolmasky <tolma...@gmail.com> wrote:
> One language addition we've been wanting to make for a long time is a
> "fixed" for-in loop, akin to "fast enumeration" in Objective-C 2.0:http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual...
That 's not good. Maybe we need to figure out another way to make it
more elegant.
1. If you are going to have a new keyword, then it has to start with
@, or at the very least a character not currently in use in JS to
avoid clashing with existing identifiers. For example, if this
restriction didn't exist, we could consider:
each (object in array)
Unfortunately this would break if the user had defined the each
function, or just had a var each. Perhaps though, its better to choose
@each instead of @for to avoid confusion?
2. I really wish we could just override for itself. We can't though,
because plenty of code relies on the current for behavior, for
example:
for (key in array) ... <-- can't go breaking this.
I believe those are the only restrictions. Again, maybe @each is the
way to go, but I'm willing to hear alternatives.
As for the @for and @each I would vote for @each to alleviate the potential confusion
my 2 c
steve
> --
>
> You received this message because you are subscribed to the Google Groups "Cappuccino & Objective-J Development List" group.
> To post to this group, send email to objecti...@googlegroups.com.
> To unsubscribe from this group, send email to objectivej-de...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/objectivej-dev?hl=en.
>
>
How about replacing "in" with something else? Maybe a colon like in
Java, or some other preposition? "within"? "inthe"? "@in"?
On the topic of modifiers... couldn't those just be attached to the
collection classes? CPArrays already have different CPEnumerators, one
for standard iteration and one for reversed iteration, and, just like
in Objective-C, the CPEnumerator can be the object over which the
FastEnumeration is performed. Tree traversal could work similarly.
Paul
@for ( object in [array reversedArray]) { }
However, we are now dealing with first taking and reversing an array,
which is not ideal when you could just start with array.length and go
down to 0. I suppose your point is that you could have something like:
@for (object in [array inReverse]) { }
or
@for (object in [treeNode inPostOrder]) { }
Where inReverse and inPostOrder don't create new instances but rather
wrapper objects that catch the fast enumeration calls and work with
them on the underlying data. That may not be bad, but there's
something about it I don't like. For starters, they might be harder to
write and apply uniformly, and more importantly difficult to chain.
Then again, if there's a way to do it without fancy features, maybe
it's best.
> > On Wed, Dec 30, 2009 at 12:56 AM, Francisco Tolmasky <tolma...@gmail.com>
What I mean is: I'd be looking at the collection's API to figure out
my traversal order/algorithm options rather than the loop syntax, so
to speak.
I think these kind of modifiers make sense for example in the case of
the Objective-C 2.0 memory management attributes, where you configure
a low-level aspect that's detached from any API construct but rather
language or runtime specific and not when it's a distinct attribute of
certain classes.
I hope I'm making sense here :)
Cheers,
Sven
> > On Wed, Dec 30, 2009 at 12:56 AM, Francisco Tolmasky <tolma...@gmail.com>
@for ( var item : enumerator ) { }
I could just provide the right enumerator or even write my own.
Regards
Leif
@for (key, object in dictionary)
@for (object, index in array)
Or to just have:
@for(key in dictionary)
@for(object in array)
and force you to calculate the other values.
@fori(key, object in dictionary)
@fori(index, object in array)
Note trying to be consistent in ordering of key->values here
and
@for(key in dictionary)
@for (index in array)
Again trying to be consistent with what the indexes really are in each of the cases.
Perhaps not quite what Objective-C/J expects, but mebbe more consistent?
--Marc
- (CPArray)something:(id)sender
{
var objects = [sender objects]; // array or dictionary??
var result = [CPArray array];
@for(a, b in objects)
{
[result addObject:a]; // adding a key? adding the object?
}
return result;
- (CPArray)something:(id)sender
{
var objects = [sender objects]; // array or dictionary??
var result = [CPArray array];
@for(a in objects)
{
[result addObject:a]; // adding a key? adding the object?
}
return result;
Also, I really vote against the multiple returns. First, as Derek
mentions it does provide ambiguity because there are not really any
types. Second, if we use enumerators, Marc's concern can be satisfied:
- (CPArray)something:(id)sender
{
var objectEnumerator = [sender objectEnumerator];
var result = [CPArray array];
@for (var object in objectEnumerator)
{
// do something
}
}
And if you expect your sender to be a dictionary, you could use
CPDictionary's keyEnumerator to enumerate over the keys. That would
solve the ambiguity of what you are actually enumerating. (The Cocoa
Design Patterns book has a great chapter on Enumerators:
http://www.informit.com/store/product.aspx?isbn=0321535022).
Cocoa Enumerators are eligible for fast enumeration, but in most situations they would not be used. In other words, there's no need to do:
> - (CPArray)something:(id)sender
> {
> var objectEnumerator = [sender objectEnumerator];
> @for (var object in objectEnumerator)
> {
> // do something
> }
> }
You would usually just do:
> - (CPArray)something:(id)sender
> {
> @for (var object in sender)
> {
> // do something
> }
> }
Situations where you are trying to handle multiple different collection types are usually case specific, but using the objectEnumerator is probably a good tactic usually.
All that being said, I'm in favor of @for (var object in collection); This is extensible in the future if we change our minds, but we won't be able to take out the de-structuring version once we put it in.
-Ross
> Well, let's be clear, Cocoa does NOT rely on enumerators for fast enumeration. The relevant article to read is this:
>
> http://developer.apple.com/mac/library/documentation/Cocoa/Reference/NSFastEnumeration_protocol/Reference/NSFastEnumeration.html#//apple_ref/occ/intf/NSFastEnumeration
>
> Cocoa Enumerators are eligible for fast enumeration, but in most situations they would not be used. In other words, there's no need to do:
>
>> - (CPArray)something:(id)sender
>> {
>> var objectEnumerator = [sender objectEnumerator];
>> @for (var object in objectEnumerator)
>> {
>> // do something
>> }
>> }
>
> You would usually just do:
>
>> - (CPArray)something:(id)sender
>> {
>> @for (var object in sender)
>> {
>> // do something
>> }
>> }
>
>
> Situations where you are trying to handle multiple different collection types are usually case specific, but using the objectEnumerator is probably a good tactic usually.
>
> All that being said, I'm in favor of @for (var object in collection); This is extensible in the future if we change our minds, but we won't be able to take out the de-structuring version once we put it in.
Got it and it makes sense.. So in the dictionary case you're have to know that it's a dictionary and that you're getting the keys instead of the objects right?
--Marc
@for (object in array) is object based, because generally that is what
you care about. If you do need to work with indexes, it is trivial to
calculate them (just add ++index in the for loop).
@for (key in dictionary) is key based because a reverse lookup of
object to key is non-trivial, especially because multiple keys can
point to the same object:
key = [dictionary keysForObject:object] // which one???
So, in a world where you only get one value to loop over, it makes
sense that its the key in the dictionary case, because if not there
would be no way to be sure what the key was (you would have to loop
over the dictionary's keyEnumerator if you wanted to use keys, or not
use them at all)
In a world where multiple objects were introduced to begin with, I
think it would have been logical that the ordering should have always
been object, key/index. Since the object is *usually* what you care
about. The unfortunate thing about the current array/dictionary
structure is that to create generic code, you have to always do this:
@for (object in [collection objectEnumerator])
This sucks because the most used case requires extra typing, AND
because it requires you to implement a whole new method on top of your
regular FastEnumeration method. On top of that, iterating over the
enumerator is much slower than the base object, unless you code an
enumerator that is very smart (for example, the unique enumerator
subclass used in objectEnumerator in CPArray should just forward its
messages to the underlying array to keep up with normal speeds). So,
to sum up, in order to allow writing generic code with the Apple-style
for-in loop, and to allow normal for-in loops, the following is
required:
1. implementing fastenumeration protocol
2. implementing objectEnumerator
3. (optional but highly suggested) making objectEnumerator return a
specialized enumerator subclass capable of fastenumeration itself
since the generic implementation simply uses nextObject which is very
slow.
In my opinion the object, key/index system is better in a vacuum (in
other words, if we were inventing the feature before Apple did).
Perhaps its worth using this system and calling @each to avoid
confusion? I don't know, I'm really torn on this.
On Jan 15, 9:12 am, Marc Nijdam <marc.nij...@gmail.com> wrote:
> On Jan 14, 2010, at 12:15 PM, Ross Boucher wrote:
>
>
>
>
>
> > Well, let's be clear, Cocoa does NOT rely on enumerators for fast enumeration. The relevant article to read is this:
>
> >http://developer.apple.com/mac/library/documentation/Cocoa/Reference/...
> ...
>
> read more »
for item in myDictionary.items():
# Now item is a very readable (key, value) tuple
for key, value in myDictionary.items():
# Item automatically unpacked
Toss in generator support and list comprehension and then we're
talking.
:)
> > >>>>>>>>>> Objective-C functionality, I believe it is important to stay as true to...
>
> read more »
-tom