BLOG

Viewing by Entry / Main
11 August, 2005
Ruby Iterators for Actionscript Part II

There was a fair bit of interest in my post yesterday about implementing Ruby-like iterators in Actionscript, so without further ado here's the source:

 /************************
Array methods
************************/

Array.prototype.each = function(block : Function) : Array {
var l = this.length;
for(var i = 0; i < l; i++)
block(this[i]);
return this;
}

Array.prototype.filter = function(block : Function) : Array {
var l = this.length;
var result = [];
for(var i = 0; i < l; i++) {
if (block(this[i]))
result.push(this[i]);
}
return result;
}

Array.prototype.collect = function(block : Function) : Array {
var l = this.length;
var result = [];
for(var i = 0; i < l; i++)
result.push(block(this[i]));
return result;
}

Array.prototype.find = function(block : Function) : Array {
var l = this.length;
for(var i = 0; i < l; i++) {
if (block(this[i]))
return this[i];
}
}

Array.prototype.inject = function(block : Function) : Array {
var l = this.length;
var a = this[0];
for(var i = 1; i < l; i++)
a = block(a, this[i]);
return a;
}

/************************
Object methods
************************/

Object.prototype.each = function(block : Function) : Array {
var l = this.length;
for(var i in this)
if ((typeof this[i]) != "function") block(this[i]);
return this;
}

Object.prototype.filter = function(block : Function) : Array {
var l = this.length;
var result = [];
for(var i in this) {
if ((typeof this[i]) != "function")
if (block(this[i]))
result.push(this[i]);
}
return result;
}

Object.prototype.collect = function(block : Function) : Array {
var l = this.length;
var result = [];
for(var i in this)
if ((typeof this[i]) != "function")
result.push(block(this[i]));
return result;
}

Object.prototype.find = function(block : Function) : Array {
var l = this.length;
for(var i in this) {
if ((typeof this[i]) != "function")
if (block(this[i]))
return this[i];
}
}

Object.prototype.inject = function(block : Function) : Array {
var l = this.length;
var a;
for(var i in this)
if ((typeof this[i]) != "function")
if (a == undefined)
a = this[i];
else
a = block(a, this[i]);
return a;
}

Here's some notes about the purpose of each method:

each
Pretty self explanatory - call the callback for each value (not the index) in the array or object.
filter
Return an array containing those items for which the callback returns true
collect
Return a copy of the array, replacing each item with the return value of the callback when passed that item
find
Return the first item for which the callback returns true
inject
"Inject" all the items in the array or object into a single variable. The call back is passed it's previous return value (or the first item when called the first time), and the current item, and combines them into a new return value

The original Ruby iterators come in multiple flavours - some replace the original array or object elements rather than create a copy, others return the index/key rather than the value.

There's a bit of extra logic in the object versions of the methods to ignore functions - without this the iterator methods themselves were included in the results. This may not be the desired behaviour - sometimes you want to iterate over functions - but it wouldn't take much tweaking to check the name of the function before skipping it.

I should mention that I also tried adding a times() method to the Number prototype - the idea being that you could write

5.times(function(){...})
to repeat something five times for instance. It actually sort-of worked, but only for numeric variables, e.g.
x = 5; x.times(function(){...})
which wasn't that useful.

So there you have it - although it doesn't stop there. Taking this callback approach further I was thinking that you could achieve similar outcomes to say ColdFusion custom tags using the function as the tag body. I should also mention that this isn't the first attempt to combine Ruby and Flash - try Googling "Rich Kilmer" (of ActionStep fame) and "Alph".

Cheers,
Robin

This is really cool thanks for sharing!
Comment made by Ryan / Posted at Friday 12 August, 2005 12:08

It looks like you could just decorate the Object class. Array will implicitly decorated as a result. Only problem is the elements will not necessarily be called in order.
Comment made by Scott Hyndman / Posted at Friday 12 August, 2005 12:08

Any idea on how to create ColdFusion CFC versions of these? Can you pass in a function block to a CFC argument? BTW, really cool stuff!
Comment made by David Ringley / Posted at Friday 12 August, 2005 02:08

For those of you who get a warm fuzzy feeling from encapsulation (who doesn't?), I put this into a class: http://rafb.net/paste/results/UeIHYe56.html
Comment made by Ben Jackson / Posted at Friday 12 August, 2005 05:08

Kewl I will give this a go when I get home tonight Thanks Robin! Cam.
Comment made by Campbell / Posted at Friday 12 August, 2005 07:08

Could someone repost Ben's class?
Comment made by Eric / Posted at Wednesday 11 January, 2006 03:01

Here you go: http://www.unfitforprint.com/articles/2006/01/10/ruby-iterators-for-actionscript-part-iii Enjoy :D
Comment made by Ben Jackson / Posted at Wednesday 11 January, 2006 06:01

To get the "5.times(fn)" to work you have to write "(5).times(fn)". The JS/AS lexer doesn't expect methods calls to number literals, but the parentheses makes the number to an expression, which can always have methods called on it. This hasn't anything to do with the runtime, just how the language is specified. For some reason, specification doesn't include method calls to number literals. An odd thing I discovered while confirming that what I just wrote is correct (using FireBug) is that "5.5.times(fn)" works fine. I guess that the first "." is always interpreted as a decimal separator, but any subsequent dots are not treated as part of the number literal.
Comment made by Theo / Posted at Wednesday 30 May, 2007 09:05

Oh, yeah, and "5..times(fn)" works too (notice, two dots).
Comment made by Theo / Posted at Wednesday 30 May, 2007 09:05

Post Your Comments