Functional ActionScript – Part II
Welcome to the second part of my series on Functional ActionScript. Part I was a brief introduction to some concepts of functional programming in ActionScript. In this second part, I will present you some examples to ActionScript's built-in functional APIs on Array. However, first I would like to introduce you to a neat little trick that will save us some typing and make our code more clear.
Foreplay
If you take a look at the documentation of the following methods that we will discuss later (every, some, filter, forEach, and map) you will notice that they all take a callback that, apart from the return type maybe, has a signature that looks like this:
function callback( item : *, index : int, array : Array ) : *
function wrap( f : Function ) : Function { return( function( x : *, index : int, array : Array ) : * { return f( x ) } ) }
Basically, it takes a simple function like:
function even( x : int ) : Boolean { return x % 2 == 0 }
…and returns a function which conforms to the callback signature shown above. Another great example for the power of higher-order functions.
The Party
After having been introduced to friend number one, namely map, in Part I, I suggest we get to know some new friends but first a small convention:
trace
I will use the following convention to denote trace output:
//?
Friend Number Two: every
If you want to check if all the elements of an Array satisfy a certain condition, just write a test function and drop it into Array.every.
Example: Everybody Even?
For example, let's see if all integer in
listare even:var list : Array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]First, we take the
evenfunction from above which takes anint, tests if it's even and returns the correspondingBoolean:function even( x : int ) : Boolean { return x % 2 == 0 }Then, wrap
evenwithwrap— doh! — drop it intoArray.everyand see what happens:list.every( wrap( even ) ) //? false
Friend Number Three: some
Array.some works along the lines of every but returns true as soon as one of the elements passes the supplied test.
Example: Anybody Odd?
In the following example, we check if any (meaning: one or more) of the elements in
listis odd:var list : Array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]Our test function:
function odd( x : int ) : Boolean { return !even( x ) }The test:
list.some( wrap( odd ) ) //? true
Friend Number Four: filter
Array.filter is really handy. Pass it a test function and it returns you an Array with all the elements that passed the test.
Example: Who's Even, Who's Odd?
Get all even elements in
list:var list : Array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] list.filter( wrap( even ) ) //? 2,4,6,8…and all odd elements:
list.filter( wrap( odd ) ) //? 1,3,5,7,9
Friend Number Five: forEach
Array.forEach is pretty much the same as Array.map with a subtle but important difference: forEach executes a function on each element in an Array but unlike map has not the purpose to modify the elements. Therefore forEach returns void and map returns an Array. This may or may not sound confusing. However, the following examples will make the difference clear…
Example: Hello
Let's say hello to all elements in
list:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var list : Array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] function sayHello( element : *, index : int, array : Array ) : void { trace( "Hello, Number", element ) } list.forEach( sayHello ) //? Hello, Number 1 //? Hello, Number 2 //? Hello, Number 3 //? Hello, Number 4 //? Hello, Number 5 //? Hello, Number 6 //? Hello, Number 7 //? Hello, Number 8 //? Hello, Number 9In this example I purposely didn't use my carefully crafted
wrapfunction from above to show you how ugly the callback function can end up (line 3–6).
Old Friend: map
We've already met map in the first part on Functional ActionScript but I allow myself to introduce her here once again. Array.map takes a function, applies it to all elements in an Array and returns an Array with all modified elements.
Example: We're Square
Square all elements in
list:var list : Array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] function square( x : Number ) : Number { return x * x } list.map( wrap( square ) ) //? 1,4,9,16,25,36,49,64,81…or take the square root of all elements:
function squareRoot( x : int ) : Number { return Math.sqrt( x ) } list.map( wrap( squareRoot ) ) //? 1,1.4142135623730951,1.7320508075688772,2, //? 2.23606797749979,2.449489742783178, //? 2.6457513110645907,2.8284271247461903,3
Friends Forever
When I'm talking about friends, I actually mean friends. Not only will the functions above be nice to you but they also get along very well with each other. Let's see how…
Example: Rendez-Vous
Let's look at this real-world scenario: If any of the elements in
listis odd, you want to pick out the even elements, square them and then say hello to them. No sooner said than done:
1 2 3 4 5 6 7 8 9 10 11 12 13 var list : Array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] if( list.some( wrap( odd ) ) ) { list.filter( wrap( even ) ) .map( wrap( square ) ) .forEach( sayHello ) } //? Hello, Number 4 //? Hello, Number 16 //? Hello, Number 36 //? Hello, Number 64Isn’t the expressivess of this code just beautiful?
Finding a more useless example is left as an exercise to the reader.
Doggy Bag (a.k.a Source Code)
Like what you saw? Have look at it, download it, and play with it!
Source
Thank you for your attention and stay tuned for Part III of Functional ActionScript…
31.03.2008
7:21
About those two last arguments to map/every/some/forEach: instead of wrapping you could just write your function like this, using the variable number of arguments feature of AS3:
function( item : *, …ignored ) : * {
// do the magic
}
“ignored” could of course be anything if you don’t want to be so negative… “rest” or “args” would do as well.
03.04.2008
22:42
[...] « Previous [...]