Wednesday, December 2, 2009

with: The World's Most Misunderstood Statement

Update If after this post and its comments you are not convinced yet, have a look into with: Some Good Example one.


Every reference to this post is purely casual ... no it's NOT!
It's time to talk about the fuss around the innocent with statement, and why somebody decided it's bad or it should not be part of next JavaScript.

Extends the scope chain for a statement

Extended chains are probably what we love more about JavaScript libraries ...

$("stuff")
.more()
.again()
.somethingElse()
;

but for some sadistic reason we decided that we don't like native extended chains in our code ...

with(stuff){
more()
again()
somethingElse()
};


Bad Examples And Bad Usages

Every time I say that with statement has nothing bad, somebody perpetually points out this bloody post ... OK, from Yahoo! ... so what?
Honestly, I could post thousands of bad examples and point my finger into this or that library, function, method, evaluation, etc etc ... it's like the myth eval is evil ... so how come one of the first one to talk about evil is using eval to speed up code interpretation?
Easy: there are cases and cases!

The Power Of with(){}

I give you the most basic example ever, something happened in twitter few minutes ago, OK?
I wanna use the smallest amount of characters to obtain a script injection ... right?

with(document.documentElement)
insertBefore(
document.createElement("script"),
firstChild
)
.text = "alert(1)"
;

Elegant, memory aware (no variables declarations, no need to create a closure), compact, efficient, cross-browser ... where are "JavaScripters" that used with to assign variables here?

with has been used since first ECMAScript 3rd Edition implementation and nobody has never had a single problem. Moreover, if we contest that we could cause a wrong variable assignment, what about a required closure plus a required var assigned to a possible node causing possible leaks plus name conflicts?

We remove a statement but we don't remove those developers that constantly forget to define local variables so they can destroy the global namespace?
for(i = 0; ... ) // FIRED FFS!!!

I would go for the second specie before the first one!

In any case, I'd love to see who is able to produce a code smaller than the precedent example taking care about removing any trace of that operation in the current scope.

Pointless Points

Somebody could say: "doode, you are not sure that insertBefore is the document.documentElement " ... The classic piece of antagonist code would be 99.9% of cases this one:

// avoid global scope pollution
(function(e){
e.insertBefore(
document.createElement("script"),
e.firstChild
).text = "alert(1)"
;
// feeling cool and clever
// just to avoid "var"
})(document.documentElement);

Wait a second ... in the compress everything to improve performances Web era we need to create a closure and a scoped variable over greater number of characters? ... REALLY? And what about the "non being sure point", does anybody check if document.documentElement, always present, has an insertBefore method? NO because sometimes we simply don't have to!

Assignments

If we willing to remove a historical valid statement as with is because people could assign stuff there and they don't know where the stuff is assigned, we should remove every function able to evaluate code from a scripting language as JavaScript is.
It's like blaming features because some silly junior developer could cause disasters:

<textarea
onchange='eval(this.value)'
></textarea>

... we cannot be serious here, can we? So, remove textarea HTML element because it could be used from somebody to write malicious code directly evaluated when the content changes ... is this the ES5 philosophy? I do hope no, but apparently, and at least for the with statement, it is.
Maybe we should consider the fact that if we have not control over our objects and scopes we can remove every evil thingy but the bad code will be still there ... isn't it? ... but it's a language fault, right?

Real Reasons To Remove with()

Compilers, munger, minifier, however we call them, these analyzers have hard life because of with statement.
The reason is that these parsers do not consider a with statement as an in-line scope and cannot predict in that moment the accessible outer scope, unless these parsers won't be able to virtually replicate every possible scenario in that scope.
Furthermore, since some compressor could optimize properties access, bytewise speaking, a with statement will make that optimization redundant:

myO.doStuff(1);
myO.doStuff(2);
myO.doStuff(3);
// sometimes enough to decide
// doStuff could be optimized via
myO[s](4);
// and here it cannot
// or it requires extra effort ...
with(myO)
doStuff(5)
;

If we use the string shortcut in the statement we'll assign the variable or raise an error (if the string is for some reason hard coded).
Another example is property access optimizations typical via ordered namespaces as YUI! dojo Closure or other libraries have.

MyLib.namespace.stuff.doSomething(1);
MyLib.namespace.stuff.doSomething(2);
MyLib.namespace.stuff.doSomething(3);
// enough in this scope to optimize access via
var s = MyLib.namespace.stuff,
d = "doSomething"
;
s[d](4);
s[d](5);
s[d](6);

Can you spot the difference? And what if in the middle of the closure there is a with?
These are just few examples where a statement like with could become uncomfortable ... but why nobody else has ever had this problem?

Implicit this

If we would like to compare a closure with a classic Object Oriented Programming world, we could consider that most of the languages uses implicit reference inside methods.

function plusPlus(void):MyClass {
++i;
return this;
}

That "i" is referencing an instance variable and it does not requires the "this" prefix. So how come implicit scope is permitted in many other languages, Java and C# included, but we don't want an in-line implicit scope in JavaScript, as with statement basically is?

Python way

There is a basic rule in the Python local/global variable choice: explicit is better than implicit ... and I cannot agree more:

class MyOne():
value = 0
def add(self, i):
# requires to be explicit
self.value = self.value + i
# otherwise it will generates an error
# since value, as local, has not been defined
# value = value + i

value = 5

o = MyOne()
o.add(1)
o.value

To avoid the error we need to define the value as global one:

global value
value = value + 1

In JavaScript we have the opposite scenario, what has been explicitly declared as local, arguments included, is local, everything else is considered from outer scopes or, if nothing there, global. It could sounds messy but this is all about JavaScript and closures so if we don't like it, why are we using JavaScript? Just use GWT or whatever hybrid way to end up with translated closures ... no? I see ... we think and we feel we are truly programming in Java, as example, rather than JavaScript, don't we? ... so, how come this unicorn became the most used programing language?

Anyway, we are not here to decide who made the best choice, we are here to discuss why with statement is still present in other programing languages, right?
The answer is simple: to make things simpler!
This Fredrik Lundh post is the first one I found searching an example ... so, does with make sense?

If We Need It, Create A Reference

Imagine we have a global open function able to return a File instance ... now imagine we justify python because there is an implicit "as" to reference that pointer ...

// Python with style simulation
with({f:open("x.txt")}){
var data = f.read(1);
}

// if we need a temporary variable
// but we would like to be sure about
// variables in the scope
with({f:open("x.txt"), data:null}){
data = 123;
// do other stuff
}

But what is wrong if we are sure that we are dealing with a variable and its method?

// I don't want to care
// about variables declaration
// neither about leaks
// scope, names, etc ...
with(open("x.txt")){
if(read(1) === "+")
write("-")
;
close();
}

In few words, there are really thousands of bad examples online and rarely good one but the blamed one is always the with statement rather than people unable to understand it.
Another example, still twitter, few minutes ago ...

var obj = {};
obj.var1 = "objvar1";
var var1 = "globalvar1";
var var2 = "globalvar2";

// so we chose the object to use ...
with(obj) {
// .. and we have no idea what kind of object is it?
print(var1); // the object one, EASY!

// if we think obj should have a var2
// property but it does not
// we are doing wrong in any case
// and we don't know our instances
print(var2); // scoped var, EASY

// if we know what we are dealing with
// we have already resolved this problem
// knowing var1 and var2 values
var1 = "bar1";
var2 = "bar2";

// this is quite obvious ... isn't it?
// we have assigned these references one line ago
print(var1); //'bar1'
print(var2); //'bar2'
};

// is there anything unexpected here?
print(var1); // 'globalvar1'
print(var2); //'bar2'
print(obj.var1);//'bar1'
print(obj.var2);//undefined


As Summary


At the end of the day these developers scared by with do not simply use it, while developers able to use it in a proper way should say good-bye because somebody else decided that with statement should not be part of ES5: WTF ... I mean, am I the only one able to spot the beauty of the with statement here?
We could find solutions for arguments.callee but with statement ... guys, please be serious about that, and ask every other programing language to remove it, while somebody could find even the good old goto useful.
Remove features, for what? Performances? Who cares if it is rarely used, I do care if it is not possible to use anymore.

Even More Evil

with(document)
with(documentElement)
insertBefore(
createElement("script"),
firstChild
)
.text = "alert(1)"
;

No comments:

Post a Comment