Friday, August 28, 2009

Scoping Strategies

I just fixed a lurking bug in my code today that was related to ActionScript's scoping rules. The bug wasn't obvious to me when I first wrote it, and took some head-scratching when I fixed it. So I thought I'd post in in case this behavior isn't obvious to anyone else eading this (say, coming from a Java background like I do, where this behavior doesn't exist).

I'll boil it down to a simpler 'puzzler' question:

What do you think this prints out?

            var i:int;
           for (i = 0; i < 2; ++i)
           {
               var blah:Object;
               trace("blah = " + blah);
               blah = i;
           }

Or how about this?

            for (i = 0; i < 2; ++i)
           {
               var blarg:Object = null;
               trace("blarg = " + blarg);
               blarg = i;
           }

(I'll put the answer at the end of this post, so that I don't blow the surprise for anyone wanting to figure it out before proceeding.)

What's going on here is that the scope of these variables (like all variables in ActionScript) is at the level of the function. That's right: even though the variable is declared inside the for() loop, they are scoped to the overall function. There are various implications and consequences that come from this scoping rule, but the specific one we're dealing with here is that that scope is also the place where the variable receives its implicit assignment. So in the case of the first example above, blah is assigned the default value of null just once. When we come around to the variable declaration again, it does not get a second implicit assignment, because it's as if the variable were declared at the top of the function, since that's its scope.

Meanwhile, in the second example, blarg is given an explicit assignment. This means that every time around our for() loop, we assign the value of null to blarg, so that the variable is always initialized to that value at the point where we declare it in the code.

To my Java developer eye, these looked equivalent: an implicit assignment would happen at the same time as an explicit assignment. But now I realize that if you really want a variable to have a specific default value, assign it explicitly or suffer the possible consequences.

Moral of the Story: Always assign a default value to a variable, especially iuf the variable is declared in a loop where you expect it to have some default value.

Addendum: To test this behavior in Java, I tried to write similar code to see the results. It turns out that the code wouldn't even compile; accessing the variable (even to System.out.println() it ) without declaring an initial value for it threw a compiler error. I could swear this type of code used to work, but perhaps they tightened up their variable declaration policy since I used that approach.

Answers:

The first example prints out:

    blah = null

   blah = 0

and the second example prints out:

    blarg = null

   blarg = null

20 comments:

DarinCA said...

Congrats on conquering this bug, Chet!

senocular said...

Chet, let is the new var!

Murat said...

As far as I remember Java (at least since 1.4) never allows you to create a variable without initial value in function scope.
This, i believe, is a great example to show people as is not java or c#, it has its own tradion and culture..

Chet Haase said...

@Murat: I think you're right. Maybe Java has never allowed this behavior in function scope. I think I was recalling variable declarations at the class level, which may have different rules than functions.

But now I know why not to take advantage of this lazy-coding approach in AS - give those variables values!

Unknown said...

Hi Chet, I am trying to figure out how to put resize effect on a native AIR window. This is how it should work as you start the app a small window will pop up with sign in form. Once the credential is successfully submitted, the native window will resize and get much bigger. I want this resizing to be smooth and blow out instead of moving to a new location and then resize. Currently the window jump from small size to the bigger one since there is no effect. How do I solve this problem?

zlionsfan said...

I might be mistaken, but I was under the impression that you could create variables in Java without setting an initial value ... the error would occur, as you found, at compile time when trying to access the variable before assigning it a value.

It's a semantic distinction, I suppose (but you could declare a variable in Java, not give it a value, and never use it - bad practice, though). And it detracts from your point, which I also think is a good one: no matter what the language is, don't use implicit values if they are available. Assign it yourself or beware.

Chet Haase said...

@Viv: I got this to work by animating the 'bounds' property on the nativeWindow object of AIR's WindowedApplication object. Animating the x/y and width/height will also work (by using Move/Resize or Animate), but I found that the bounds property avoids some jittery artifacts by setting width/height and x/y atomically (actually, the location still seems to be set asynchronously from the size, but at least x/y are set together and w/h are set together).
There were some other details to getting it to work nicely with compact code. I'll save the implementation for one of my videos; I needed some more demo ideas, thanks!

@zlionsfan: Yes, I think you're right - the compiler only complains when trying to access the unset variable. Meanwhile, AS is pretty happy to do what I told it to; just use the default assignment (at declaration time, which is hoisted to the function level).

markval said...

var myObj:Object = new Object();
or
var myObj:Object = {};

Chet Haase said...

@markval: Not sure what question you're answering (or asking?). There wasn't a problem with how to initialize an object, but rather the fact that you really should initialize it to avoid getting bitten by the function-scope of variables.

Unknown said...

Hi Chet I am trying to apply the following resize/mode parallel effect on the air
native window ---- sorry your blog do not accept mxml code since it thinks it is html code. So I put my one line code in wikipedia and the link is in the below. The sub section of the page is called "my code" where I put the code and error message

http://en.wikipedia.org/wiki/Talk:Adobe_Flex#my_code

What is wrong with this code and how do I solve this?

Regards....

Chet Haase said...

@Viv: Move (and the other transform effects) only work on components and graphic elements. They use some transform functions on those objects to do their job, so they won't work on arbitrary objects. So while Flex 4 effects in general can work on arbitrary objects, the transform effects in particular need to know more about the objects in question to succeed.

I made this work (and shot a video on it just yesterday, in fact...) by using Animate directly to animate the x, y, width, and height properties of the nativeWindow. I also had better luck by setting the bounds property on nativeWindow so that the properties are set more atomically instead of one-by-one.

Unknown said...

Thanks Chet for the reply. I just went and saw that your Pixel Bender tutorial is up on tv.adobe.com, which is a new addition to codedependent series, I guess. When the tutorial that was recorded yesterday will be uploaded? I believe that you do not upload your video tutorials in your blog separately - just embed the video from adobe TV. In that case I will have to wait for Adobe TV!!!!

Markus said...

I'm quite sick of tracking down bugs due to scoping and the fact that you can use variables before they are even declared:

var x = y + 2;
var y;

I've started to use array.forEach instead of "for each" just to get the block-scope for loops:

myArr.forEach(function (o:*,i:*,a:*):void {
var x; // a variable only available in the loop!
});

// x is undefined here

Sol Wu said...

Chet, thank you for the post on scoping rules.
We as a community may wish consider to tighten actionscript syntax rule to enforce initialization of variable.

Sol Wu said...

@Murat, Chet: yes, Java compiler does not allow method variable to have no initialization (for a long while), but does allow class members to have no initialization.

Sol Wu said...

By the way, I came from Java background and I thought it might be worthwhile to point out this:

In Java, we commonly declare i in for loop so that i is scoped inside for loop.

eg.
for (int i=0; i < 10; i++) {
// i can only be accessed inside this for loop
}



If we do something similar in actionscript, the i is actually in function scope and can be accessed anywhere in that function.

eg.
for (var i:int=0; i < 10; i++) {
}

i can be accessed outside of for loop.


hope it helps.

Chet Haase said...

@Sol Wu: Thanks for the feedback and further info.

I'd run into that for() loop scoping issue early on (as most people probably have. The first time you try to write a second loop in a function with the same inline declaration of i, the compiler throws warnings and you learn about variable scoping).

The one I wanted to highlight in this blog was the more subtle one about the declaration/instantiation time, as it wasn't obvious to me until I hit the bug and worked through it.

I wouldn't mind seeing this tightened up in the long run either, although language changes (in any platform) take a long time to work through, so it's good to understand how to work with what you've got in the meantime.

Sol Wu said...

@Chet That's true. The one I pointed out is probably much more obvious.

Sol Wu said...

By the way, I just found out about this blog from Vivien's email to mailing list. Wow, this blog is a complete and total gem for effects! :) Thanks for all the awesome articles, Chet.

Murat said...

@sol I am not sure if actionscript needs tightning up, AS became quite mature with as3. However it is just a new language, it has its own rules and traditions which is perfectly ok. Such changes usually break up backward compability besides those actionscript also has its own pros. Just do not you are still in JVM, it is a different language and Chet's post is good example to see those differences..