Do Arrays and Objects have side effects?

In my last post about static initializers I talked about member initializations with side effects, which need to be neutralized. This was one of the examples for “safe” initializers without any side effects:

// ActionScript:
public class Foo
{
    public const m_instance_member : String = "Instance Member";
}
// JavaScript:
var Foo = function() {};
Foo.prototype.m_instance_member = "Instance Member";

The example above used a String on purpose. As it turns out Arrays and Objects are not always safe and may introduce side effects in situations you might not expect.

 

Ninjas are Persons too

I have a simple question for you: Are Arrays safe, too?

// ActionScript:
public class Person
{
    public var m_array : Array = [];
}

Looks pretty safe, doesn’t it? Shouldn’t we transform that snippet as follows?

// JavaScript:
var Person = function() {};
Person.prototype.m_array = [];

That also looks okay. But problems appear when you use Person as a base class for another class.

// ActionScript:
public class Ninja extends Person
{
}

 

// JavaScript:
var Ninja = function() {};
goog.inherits( Ninja, Person );

What can possibly be wrong with this transformation? Well, try this code:

// ActionScript:
var person : Person = new Person();
var ninja : Ninja = new Ninja();
person.m_array.push(123);
trace( ninja.m_array.length );

 

// JavaScript:
var person = new Person();
var ninja = new Ninja();
person.m_array.push(123);
trace( ninja.m_array.length );

We create an instance of Person and an instance of Ninja. Then we add the number 123 to person‘s m_array and print out the length of ninja‘s m_array. The expected result should be of course 0. So, what’s the problem? Unfortunately, if we don’t treat Array as an initializer with side effects the generated code will yield 1 instead of 0. For some odd reason adding 123 to person‘s  m_array somehow affects ninja‘s m_array as if m_array were shared between the two classes.

 

Closure-library’s Issue 396

Last month I wrote up this bug against Google’s Closure Library, which was filed as issue 396:

Issue 396: goog.inherits() does not clone arrays and objects
What steps will reproduce the problem?

1. Run this code, which uses goog.inherits
var Person = function() {};
Person.prototype.m_array = [];
var Ninja = function() {};
goog.inherits( Ninja, Person );
var p = new Person();
var n = new Ninja();
p.m_array.push(123);
console.info( n.m_array ); 

2. Observe result in the debug console
Result is: "[123]"

What is the expected output? What do you see instead?
"[]"
The code in #1 is pretty much identical with the code I introduced above. So why is the result “[123]” and not “[]”? Here is what the friendly folks from Google told me:

Nicholas J. Santos: right, this is working as intended. All objects on the prototype are shared. I believe Closure Linter will warn you about this if you do it.

Jay Young: It sounds like you expect goog.inherits to copy members from one class to another (also known as mixins or composition-based inheritance). That is not the case. Closure uses true prototypal inheritance. Anything on a superclass prototype is shared with all subclasses as well. Closure style has a rule that only primitive values should be placed on the prototype chain. Most often, the intention is to use an empty object/array as a default, but what ends up happening is exactly what you described: methods end up mutating the prototype object instead of an instance-specific one.

I hope Nicholas and Jay will forgive me, because I submitted this problem as a bug even though I had already known that this wasn’t a bug. I just wanted to get an official explanation that I could share with you. It might not be worth diving into every detail. But the problem lies in the way goog.inherits emulates classes by merging prototype chains. I suspect that every class emulating JavaScript library has that problem. I know that John Resig’s simple JavaScript inheritance code does.
But how do we fix this problem?

 

Neutralizing Arrays

You might have already guessed the answer. Even though Arrays and Objects don’t have any side effects per se we should still treat them as initializers with side effects, because Arrays and Objects get shared when using prototypes for emulating inheritance in JavaScript. Fortunately we know how to neutralize side effects.
Here is the complete example:
// ActionScript:
public class Person
{
    public var m_array : Array = [];
}
public class Ninja extends Person
{
}

 

var person : Person = new Person();
var ninja : Ninja = new Ninja();
person.m_array.push(123);
trace( ninja.m_array.length ); // Expected: 0 and not 1

And here is the transformed JavaScript:

// JavaScript:
var Person = function()
{
    var self = this;
 self.m_array = [];
};
Person.prototype.m_array = null;

 

var Ninja = function() {};
goog.inherits( Ninja, Person );

 

var person = new Person();
var ninja = new Ninja();
person.m_array.push(123);
trace( ninja.m_array.length ); // Expected: 0 and not 1

 

Conclusion

Arrays and Objects introduce side effects when used as static initializers in base classes, because most JavaScript libraries chain prototypes for emulating inheritance. For that reason I recommend adding Arrays and Objects to the list of expressions with side effects when used as static initializers, which need to be neutralized.


5 Responses to Do Arrays and Objects have side effects?

  1. Rock den says:

    Hey, I think your site might be having browser compatibility issues. When I look at your blog in Safari, it looks fine but when opening in Internet Explorer, it has some overlapping. I just wanted to give you a quick heads up! Other then that, wonderful blog!

  2. Bernd Paradies says:

    Thanks for the warm words and pointing out those problems in IE. I will check with our IT department. But a lot of folks are already on their Holiday break. This might not get resolved until the new year.

  3. Rahan says:

    Hi all,
    About this cross compilation issue.
    I have more global questions which are very important for strategic future of the flash platform.

    You’ talking about FalconJS and cross compilation to actual JS, aka EcmaScript 3.
    But the near future and the very usefull cross compilation of AS3 for mobile web browsers where theres no flash player, will be for sure the cross compilation to Ecmascript 6. would’nt it?

    So i wonder:
    – how is it difficult to cross compile AS3 to ES6? what performances drawbacks it leads to?
    – what is the gap between ES6 and ES4 and thus, how long will it take for standards to evolve from ES6 to next version?
    – what the drop of packages, namespaces and early bindings imply for AS3 cross compilation and performances?

    Thanks a lot, for the answers which are very very important to understand where flash platform and AS3 coding can lead to the near future.

    • Bernd Paradies says:

      Hello Rahan,

      FalconJS is designed to generate JavaScript, that can be processed by most modern browsers like Safari, Chrome, Firefox, and IE9 (sorry, but IE6 doesn’t make the cut). Early versions of FalconJS used ES5’s getter/setter but that feature turned out to create massive performance problems. FalconJS currently identifies getter/setter at compile time and injects appropriate calls, which avoids the performance problems of ES5’s getters and setters.

      In general I would say that adjusting FalconJS to emit ES5 or ES6 JavaScript should not be difficult. But I suspect that in many cases it pays off to let FalconJS statically analyze the code and emit code that avoids ES5 and ES6 features. Please keep in mind that many of those new ES5/ES6 features don’t come for free.

      It is quite possible that you might have to pay for the convenience of using ES6 features like proxy, or ES5 features like getters/setters with some significant loss of performance. Let’s hope that that will only be the case in the first years…

      In regards to avoiding resolving names at runtime to increase performance (if that was one of you question) I propose using the Dart development model, where in “checked mode” FalconJS would emit fairly expensive code, which would throw asserts but which would get stripped out in “production mode”. I discussed this idea in my very first blog post called “Dart and Types: Tennis without a net?” (http://blogs.adobe.com/bparadie/2011/11/16/dart-and-types-tennis-without-a-net/).

  4. Bernd, just wanted to mention that Jangaroo has been applying this pattern for initializing fields with Arrays or Objects (or in fact anything that is not immutable) from the very beginning. I’ll try and put together a blog post about how Jangaroo even keeps the initializing code at the same line number in the generated output.