Wednesday, December 02, 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)"
;

15 comments:

Karl said...

Alas, I'm writing "use strict" javascript.

Andrea Giammarchi said...

Use strict means simply future proof code and language direction

Michael Haufe ("TNO") said...

These couple examples show some more evil:
-----------------------
var x = 42;
var o = {x:33};
with (o)
var x = 21;
print('var x is ' + x + ', o.x is ' + o.x);
--------------------------

also:
--------------------------
function foo(o){
var count = 0,
result = 0;
with(o){
//stuff...
for(var i = count; i < 10; i++){
result++
}
//stuff...
}
return result
}

//stuff...

var o = {
count : 100,
stuff:"more stuff"
};

var j = foo(o);

console.log(j)
-------------------------

Andrea Giammarchi said...

bad examples + bad usage - I have described these kind of examples, don't use with in that way, preserve with usage

Andrea Giammarchi said...

P.S. nothing unexpected to me though ...

Michael Haufe ("TNO") said...

Patient: "Doctor, it hurts when I do this.."
Doctor: "So don't do that".

This discussion reminds me of the one that happened last year on es-discuss. The summary of which you can see here:

http://wiki.ecmascript.org/doku.php?id=es3.1:with_statement

If you truly believe this is a major mistake, I would implore you to be more pro-active and participate in es-discuss in order to make your point where it could more likely matter.

In regards to your comment about other languages having "with", there is a significant difference. In other languages you denote a property lookup with the prefix ".". If the property is not found in the provided argument, it throws. To do something similar in JS, you would have to force semicolon usage in order to enable the use of a "." prefix (and fix the problem with variable declaration). Since forcing semicolon use is not the case, and a leading "." is not an option, every reference has to keep walking up the chain to figure out what it is, if anything.

Andrea Giammarchi said...

did you read the full post or just a piece of it?

with({o:myreference}){
o.doStuff();
};


easy? Above snippet is exactly the same you are talking about but I have described already these cases in this same post.

using var inside a with is not a real case scenario. We can do all kind of mistakes with whatever programming language if we don't know what we are doing.

That ML does not listen developers, I have tried before to contribute but rarely somebody tried to even understand what I was talking about.

Thanks for your reply though, you have demonstrated what I have wrote: people keep presenting bad examples and bad usages thinking we are all idiots and we will use the statement in that way.

Patient: "Doctor, it hurts when I use the hammer with my head"
Doctor: "The hammer has been created to do something else".

This is the discussion, the rest is just about misunderstanding the with statement.

Regards

Michael Haufe ("TNO") said...

"using var inside a with is not a real case scenario."

I think it is, and here are just a few examples pulled from google.

So the feature is rarely used, and when it is, its sometimes done so improperly. It doesn't compose well with other languages constructs and it prohibits static analysis. You can also still use it in non-strict es5 code.

Patient: "Doctor, it hurts when I use the hammer with my head"
Doctor: "The hammer has been created to do something else".

In this case the doctor figured it was better to take away the hammer.

Andrea Giammarchi said...

In this case the doctor figured it was better to take away the hammer.
So that professionals cannot use the hammer in a proper way anymore, right? #FAIL

If this is ES5 philosophy every JavaScript Junior should be forbidden ... nice one but I totally disagree.

The doctor should teach how to use the hammer, rather than keep showing the head is not the only place where the hammer hurts badly.

coder-zone said...

Totally agree, the article talks to deprecate ALL hammers, not only the one in the hand of the patient.

Harsh as it may sound, if the patient is stupid, is his own fault. Let ban him from enter hardware stores, but let the rest of the world have fun with their hammers.

Metaphors apart, this "with({o:myreference})" is a tiny masterpiece. Thanks Andrea, it will help me to improve my code in so many places...

abozhilov said...

For a first time, i read something good in your blog. Whatever. Only you missed to explain how it works `with' statement. How `object' will be put in the front of the Scope Chain and how resolving identifier against scope chain.

Regards.

James Padolsey said...

I have to agree with Andrea here. "with()" can be both _powerful_ and _dangerous_.

Don't punish the whole class just because a couple kids are messing around!

Roy said...

with's main function is that it adds a particular value to the scope chain.

I would argue, by contrast, that it's better to avoid referencing things outside the scope chain, especially the global scope.

Your example:
----
with(open("x.txt")){
if(read(1) === "+")
write("-")
;
close();
}
----
would be better written as a closure:
----
(function(f) {
if (f.read(1) === "+")
f.write("-")
;
f.close();
})(open("x.txt"));
----

This code is less prone to errors -- what if the open() function returns undefined? Then code that is further up the scope chain might be accidentally called. It introduces a level of volatility to code...

This is why I intentionally wrap all my code (browser-side) in a function that references global explicitly:
----
(function(global, undefined) {
var $ = global.jQuery;

//my code here...

})(this);
----

This also increases performance for calls to $.ajax, $('...'), etc., because $ is now a local variable. Also, any time I reference the window object, I use the local "global" variable instead.

Andrea Giammarchi said...

what if the open() function returns undefined?

then my code is broken or I simply throw an error due missing permissions so that nothing will happen.

Said that, it was just an example, I don't use with since it's out of "use strict" directive and I code 100% "use strict" approved ;)

ShiChao Wang said...

+1 For Your Post, after read tens of articles including all links in your post;

Chrome source codes using 'with';

Firebug source codes using 'with';

The default template engine 'jade' of the great 'Express'(one web framework of nodejs) using 'with' statement;

Powerful 'with' using in powerful circumstance to show its power, with care.

And I don't think remove it from the ECMAScript for some misleading samples is a wise choice.

Introduce more info to the developers or improve it is much more meaningful than to blame the 'with' even to remove it;

I love 'with' and pray for it';