Be a Supporter!
DougyTheFreshmaker
DougyTheFreshmaker
  • Member since: Jul. 30, 2007
  • Offline.
Forum Stats
Member
Level 02
Blank Slate
AS3: Closures Apr. 8th, 2008 @ 01:02 PM Reply

AS3: Main

First, before I even start to get into this, I want to say that Actionscript is an ECMAScript derivative. Javascript also falls into this category, so you can often find tutorials, examples, etc which translate easily from one language to another. A VERY good explanation of Closures and a few of their uses can be found here, but is biased towards Javascript, and isn't the most accessible thing out there ;)

Second, the uses of a "closure" are not really a clear-cut. If someone asked you "what can a class be used for?", there would be no single definite way to answer the question. Closures, in fact, can almost be thought of as another way of representing a class (but don't think of them that way!), but offer other practical uses as well. In this example, we'll discuss what a closure is and then use one to help solve the common problem of "passing arguments to event handlers", as well as pick up a nifty trick or two.

Background - Know this stuff first (if you don't, look it up before continuing)

Actionscript 3.0 is a garbage collected language. When you create an object, that object will exist only as long as there are references to it. When all references to the object are gone, the object can be deleted from memory. This is usually done using a technique known as "reference counting". We need to know that this happens, but not necessarily how it's done exactly.

Reference counting fails when you have two (or more) objects which point to each other, and no other objects which point to those. For this, you employ a technique from the graph theory world called "mark and sweep". We won't be too concerned with this corner case.

Finally, we all know that functions have a certain scope. That is, if you define a variable within a function "foo()", it only exists as long as the function does:

// No x exists here
function foo():void {
    var x:int = 5;
    // At this point we have a variable, x, with the value 5!
}
// But when we get here, it's gone again!
// Subsequent calls to foo() will re-declare a variable x and remove it when the function ends

And, last thing I swear, you have to realize that functions can be declared WITHIN other functions:

// This function should trace "Barred!!" ten times (untested)
function foo():void {
    function bar():void { trace("Barred!!");
    for(var i:uint=0;i<10;i++) bar();
}

Closures

So here's the wizardry that is a closure (untested):

var reference_to_bar:Function = foo();

function foo():Function {
    var foo_string:String = "Foo's string";
    function bar():void { trace(foo_string); }
    return bar;
}

So here's the trick question of the hour. Given the above code, what would you expect to be traced to your console when you attempted to call reference_to_bar()? When you look at the foo(), it looks perfectly reasonable for foo_string to be accessible inside of bar(). The problem is, though, that it looks as if foo() has executed to completion, so foo_string shouldn't even exist anymore! So you run the code and get:

Foo's string

So what's going on here? It turns out that the function foo() actually "has" a reference to its inner function "bar()", and when you return that reference to the calling code and assign it to a variable, AS3 decides that it can't actually clean up the "foo()" function until all references to its inner "bar()" function have been released! Thus, foo_string continues to exist, and continues to be accessible to the bar() function.

Another very similar example could use an int variable to keep track of the number of times a function has been called, if you wanted to do that for some weird reason:

var reference_to_bar:Function = foo();

function foo():Function {
	var count:int = 0;
	function bar():void { trace("Times executed: " + ++count); }
	return bar;
}

Each call to reference_to_bar() displays how many total times the function has been executed.

An example

So here's an example of a small game which uses closures. In addition to the last example given, the code also uses a closure to (sort of) solve the problem of passing parameters to event handlers, a question which comes up a lot and which is usually solved by throwing variables into a class or global scope when they don't really belong there (bad).

package  {

	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.MouseEvent;

	public class ClosureExample extends Sprite {

		private static const SQUARE_WIDTH:int = 20;

		public function ClosureExample() 
		{
			// Create a square with some random color and place it randomly on the stage
			var square:Bitmap = new Bitmap(new BitmapData(SQUARE_WIDTH, SQUARE_WIDTH, false, (0xFF << (Math.random() * 24))));
			move_object_randomly(square);
			addChild(square);

			// Add our click event handler.
			// NOTE: This *calls* the mouse_click_closure(...) function, which *returns* our actual handler!!
			var handler:Function = mouse_click_closure(square);

			// Add the returned handler.  handler can be replaced with mouse_click_closure(square) if desired.
			stage.addEventListener(MouseEvent.CLICK, handler, false, 0, true);

		}

		// Randomly repositions the passed in object somewhere within an arbitrary area of the stage.
		private function move_object_randomly(object:DisplayObject):void
		{

			object.x = Math.random() * stage.stageWidth * 0.5;
			object.y = Math.random() * stage.stageWidth * 0.5;

		}

		// Our closure!
		private function mouse_click_closure(target:DisplayObject):Function
		{
			// Our mouse click handler now has access to whatever DisplayObject we pass in.
			// You can pass in as much crap as you want in this way, and it will all be available to your event handler.

			// Likewise, variables declared within this "outer" function will exist over the lifetime of the closure.
			// score is used here to track the total number of times the user has clicked "target" DisplayObject.
			var score:uint = 0;

			// This is our "inner function" which will be returned.  This function is assigned to a variable for verbosity
			// reasons and can be returned directly if desired.
			// THIS FUNCTION IS OUR ACTUAL EVENT HANDLER WHICH WILL BE CALLED EACH TIME THE TARGET IS HIT/CLCIKED
			var mouse_handler:Function = function (e:MouseEvent):void {

				// Test for a hit
				if ((target.mouseX <= target.width) &&
					(target.mouseY <= target.height) &&
					(target.mouseX > 0) &&
					(target.mouseY > 0))
				{
					
					// Increase the score and reposition the object!
					trace("Hit #" + ++score);
					move_object_randomly(target);

				}

			}

			return mouse_handler;

		} // End of mouse_click_closure(target:DisplayObject):Function

	} // End of class ClosureExample

}

The preceding code draws a square somewhere on the stage. When the square is clicked, the score is incrementend and printed to the console, and the square is randomly repositioned. Also note that there are no class-level variables (besides a constant used for readability). The 'square' variable, for instance, exsists only within the constructor and within the closure.

Hopefully the comments can help you work through what exactly is happening. As a general rule of thumb, closures are created when you return references to functions which are declared within other functions. The outer function will continue to live, with all of its variables/inner functions "in-tact", until all references to its inner functions are removed.

Unforuntately I'm hitting the character limit, so if there are questions, just ask. There are other uses for cloures as well, so be sure to Google for those, as well as other introductions (for JS too! AS3 ones can be hard to come by).


We should take care not to make the intellect our god; it has, of course, powerful muscles, but no personality.
Freshmaking
Brainscrape

BBS Signature
GustTheASGuy
GustTheASGuy
  • Member since: Nov. 2, 2005
  • Offline.
Forum Stats
Member
Level 08
Blank Slate
Response to AS3: Closures Apr. 8th, 2008 @ 01:26 PM Reply

Yay tutorial!

I should mention that as cool as they are closures are slow to create so you should avoid creating them intensively (or when you don't need to at all).

Also with a bit more code the example function could be used to provide the partial application to event handlers that everyone wants so much. (At least it's not Java.)


BBS Signature
Moonkey
Moonkey
  • Member since: May. 11, 2007
  • Offline.
Forum Stats
Member
Level 07
Programmer
Response to AS3: Closures Apr. 8th, 2008 @ 05:56 PM Reply

Interesting.

A few times I've missed having static variables within functions, but this seems like a good alternative to get the same affect.

NickZAF
NickZAF
  • Member since: Feb. 14, 2009
  • Offline.
Forum Stats
Member
Level 01
Blank Slate
Response to AS3: Closures Feb. 14th, 2009 @ 09:24 AM Reply

Dougy,

Thanks for the decent reference on function closures, it's near the top on google's results.

I'd like to share what I just achieved with function closures for those who are interested in good OO design principles:

http://www.visualharmonics.co.uk/actions cript-3/using-function-closures-in-objec t-composition/

Regards,

-Nick

GustTheASGuy
GustTheASGuy
  • Member since: Nov. 2, 2005
  • Offline.
Forum Stats
Member
Level 08
Blank Slate
Response to AS3: Closures Feb. 14th, 2009 @ 10:03 AM Reply

Cool. You've essentially reinvented monads, except in a shabby OOP language. Yay. :p

Have you actually tested the code in the blog post though? I'm pretty sure that since 'this' is resolved dynamically 'mx' and 'my' will not be resolved to the getter and setter. Those setters are incredibly useless as well, there's no point to have functions to do exactly the same as just setting it. It's no encapsulation, only needless overhead.

Doug has a (currently inactive) blog here http://www.transcendentaxis.com/dthompso n/blog/
You can also sometimes meet him in our IRC channel.


BBS Signature
LeechmasterB
LeechmasterB
  • Member since: Apr. 1, 2005
  • Offline.
Forum Stats
Member
Level 17
Blank Slate
Response to AS3: Closures Feb. 14th, 2009 @ 12:29 PM Reply

At 4/8/08 01:02 PM, DougyTheFreshmaker wrote: An example

So here's an example of a small game which uses closures. In addition to the last example given, the code also uses a closure to (sort of) solve the problem of passing parameters to event handlers, a question which comes up a lot and which is usually solved by throwing variables into a class or global scope when they don't really belong there (bad).

Uhm hello have you ever programmed AS 3.0 events? Seriously passing parameters to event handlers is solved completely different and nicer. Simply pass parameters in the event object, its even shown in the flash live docs. And thats pattern wise how its usually done (observer pattern). So you name a problem that has a known common solution and give a wierd solution to it? Thats rather confusing and not exactly the best way to do an example.

Just wanted to point out that small mistake in the text, after all it should be educational and not confusing ^^.

If you still don't believe it look up custom events, extending event ect.

GustTheASGuy
GustTheASGuy
  • Member since: Nov. 2, 2005
  • Offline.
Forum Stats
Member
Level 08
Blank Slate
Response to AS3: Closures Feb. 14th, 2009 @ 12:37 PM Reply

At 2/14/09 12:29 PM, LeechmasterB wrote: Uhm hello have you ever programmed AS 3.0 events? Seriously passing parameters to event handlers is solved completely different and nicer. Simply pass parameters in the event object, its even shown in the flash live docs. And thats pattern wise how its usually done (observer pattern). So you name a problem that has a known common solution and give a wierd solution to it? Thats rather confusing and not exactly the best way to do an example.

You missed the point. This is partial application, to do the same by holding data in the event you'd have to redispatch your custom one. The most concise approach being, I don't know, a closure?

addEventListener ('click', function (_) { dispatchEvent (new ClickActionEvent (square)); });

BBS Signature
LeechmasterB
LeechmasterB
  • Member since: Apr. 1, 2005
  • Offline.
Forum Stats
Member
Level 17
Blank Slate
Response to AS3: Closures Feb. 14th, 2009 @ 01:15 PM Reply

At 2/14/09 12:37 PM, GustTheASGuy wrote: You missed the point.

Sorry but you missed my point.

I was refering to specifically one line (which should have been marked strong/bold in the quote but the tags don't seem to have worked. I meant exactly this:

At 4/8/08 01:02 PM, DougyTheFreshmaker wrote:

solve the problem of passing parameters to event handlers

Which I stated has not been a problem really ect. And hence my point should still be valid and you simply missinterpreted what I said gust.

LeechmasterB
LeechmasterB
  • Member since: Apr. 1, 2005
  • Offline.
Forum Stats
Member
Level 17
Blank Slate
Response to AS3: Closures Feb. 14th, 2009 @ 01:20 PM Reply

And that method of passing parameters in an event object is called the push method from the observer pattern as opposed to the pull method where the observer pulls the required data out of its collaborating objects.

Really simple...

GustTheASGuy
GustTheASGuy
  • Member since: Nov. 2, 2005
  • Offline.
Forum Stats
Member
Level 08
Blank Slate
Response to AS3: Closures Feb. 14th, 2009 @ 01:25 PM Reply

That's exactly what I'm talking about. Currying 'target -> mouseevent -> effect' by applying the target. That's what the closure does, partial application.
To add the target to an event you need to redispatch the event with '\mouseevent -> dispatchEvent (ClickActionEvent square)', which is the same as using the closure except with another event in between.


BBS Signature
LeechmasterB
LeechmasterB
  • Member since: Apr. 1, 2005
  • Offline.
Forum Stats
Member
Level 17
Blank Slate
Response to AS3: Closures Feb. 14th, 2009 @ 02:08 PM Reply

At 2/14/09 01:25 PM, GustTheASGuy wrote: That's exactly what I'm talking about. Currying 'target -> mouseevent -> effect' by applying the target. That's what the closure does, partial application.
To add the target to an event you need to redispatch the event with '\mouseevent -> dispatchEvent (ClickActionEvent square)', which is the same as using the closure except with another event in between.

If you already refuse to comprehend what I wrote twice why do you keep insisting?
And also the closure approach isn't necessarily a good one in that example. And seriously I dont even feel explaining the why to stubborn people who can't even read the same thing if its rubbed under their nose twice.
Simply a matter of way too strong coupling and code glued to soemthing that it shouldn't be glued to. Not to mention the uselessness of the score variable at that place :P

GustTheASGuy
GustTheASGuy
  • Member since: Nov. 2, 2005
  • Offline.
Forum Stats
Member
Level 08
Blank Slate
Response to AS3: Closures Feb. 14th, 2009 @ 02:13 PM Reply

This isn't about OOP patterns. Mind posting a code snippet of what you're talking about?


BBS Signature