After my last post on CF and JS integration (via Rhino), I got several comments that can be summarized as "why?". I addressed the issue in a rather oblique manner in my last post; here's a more full treatment.
Before I start, I want to make incredibly clear that I'm not talking about a new JS-based application server built on Rhino. I'm talking about using JS as an alternate/supplemental syntax for your CFML applications, which still run on the ColdFusion application server, with all the niceties that the CF server provides.
The first reason is that CFML is incredibly verbose for a lot of common tasks. Consider a function definition in CFML:
<cffunction name="doIt" output="false"> <cfargument name="param1" /> <cfargument name="param2" /> </cffunction>
Now the same definition in JS:
function doIt(param1, param2) { }
The CFML is a lot more verbose, which is both more typing, and more characters to visually parse when you're trying to read the code. Of course, this isn't without downsides. Consider running a parameterized query in CFML:
<cfquery datasource="myDsn" name="get"> select * from my_table where id = <cfqueryparam cfsqltype="cf_sql_integer" value="#id#" /> </cfquery>
And now in JS:
var q = new Query("myDsn", "select * from my_table where id = :id"); var get = q.execute({ id: new QueryParam(id, "integer") });
The CFML implementation definitely highlights the SQL as a standalone language embedded within the markup, while with the JS implementation, the SQL is just a string; there's nothing SQL-y about it. Is the more concise nature of the JS worth it in this specific case? I'd say it's close to a wash: less SQL delineation, but more concise code, more easily reusable SQL, semantically relevant (instead of positionally anchored) parameters, and reusable parameters. Note that in either case, I'm running my parameterized SQL against a datasource configured in the CF Administrator, and the 'get' variable will be a native CF query object (a 'coldfusion.sql.Table' instance).
Now where I really think JS (really ECMAScript, in general) has a lot to offer over CFML is in the language constructs itself. Nested functions, function literals (and their extension: closures) are a singularly fantastic language constructs. Rather than show a single example implemented in both CFML and JS, I'm going to just illustrate the concepts in JS, and contrast them with CFML. Nesting first:
function doSomething(param) { function f(o) { // do something ... } var result = f(param); for (var i in result) { if (typeof i == "object") { result[i] = f(result[i]); } } }
So what's happening? I'm packaging up some reusable functionality into a function (named 'f'), but it's an implementation detail of the 'doSomething' function, and is therefore internal to the 'doSomething' function. Do do this same sort of "subpackaging" of functionality in CFML, you'd have to put the 'f' function outside as a peer of the 'doSomething' function (probably named 'doSomething_f' or whatever). That's a leak in encapsulation, as well as unnecessarily crowding the external namespace.
Function literals implicitly require function variables, the latter of which CF already has. A function variable just means you can pass around a function as a variable, and then call it later. A function literal, however, means you can define a function without declaring it. A simple example:
function declaredFunction() { } literalFunction = function() { };
In the former case, there is an actual declaration of the function. It is it's own statement, and is compiled (or interpreted) as such. The latter, however, is a literal. It's an expression, and can be placed anywhere you might want to use an expression. Here's another example of doing something with declarations and then literals:
// declarations function optionOne(){} function optionTwo(){} return (x == y) ? optionOne : optionTwo; // literals return (x == y) ? function(){} : function(){};
Assuming this is within another function, the declared functions in the first section could be nested, thereby minimizing their effect on namespace crowding. The second section, of course, doesn't have any extra declarations, though if the functions have any substantial size to them, it can get unreadable very quickly. As such, the latter syntax isn't always the right one, but it is nice to have the option. With CFML, you're again forced to explicitly declare the functions as peers.
And now closures. Closures are nested functions (declared or literal), which execute in the context of their definition location, even if they're executed after that context has gone away. Here's a (somewhat contrived) example:
function getFilter(name, value) { return function(item) { return item[name] == value; } } function conditionallyDoSomething(collection, filter) { for (var i = 0; i < collection.length; i++) { if (filter(collection[i]) { // do something ... } } } conditionallyDoSomething(people, getFilter("age", 27));
This is a fairly contrived example, as I said, but it illustrates the concept. By the time the function returned from 'getFilter' is executed, the 'getFilter' function has terminated. However, it (the function literal) still has access to the context it was defined in – namely the 'getFilter' function's context, and therefore it's parameters. Closures are really the crown jewel of ECMAScript, in my opinion. With CFML, the closure is totally undoable, unless you use some sort of synthesis framework like Sean's.
The final advantage, like the querying example above, cuts both ways. JS-based implementations can't use any existing CFML tools (Fusebox, ColdSpring, etc.), but they can use all the myriad JS tools that are available. I happen to be a big fan of Prototype, and I can use the UI-agnostic pieces (like the Object and Array extensions) of Prototype on the server-side. Similarly, if you write some wicked-cool routine for your server-side processing, you can immediately and directly reuse it in your client-side JS (or quite possibly AS).
So between the enormously lighter language syntax and the additional language functionality, I think there's a compelling case for using JavaScript (or ECMAScript) as a language for building apps on the ColdFusion server. In an ideal world, it's be ECMAScript 4th Edition (what ActionScript 3 is based on), but there isn't a mature, Java-based interpreter for it anywhere. Aside from just generally being more modern, the 4th Edition provides strong typing (using colon-postfix notation), real packages and classes, interfaces, object member access levels, and a few other neat things.
One very interesting aspect of all this is that Adobe happens to own an ECMAScript 4 compiler (the AS3 compiler from the Flex SDK), an ECMAScript 4 runtime (the Tamarin project that underlies Flash Player 9 and is now hosted by the Mozilla Foundation), and ColdFusion. So it seems like using ECMAScript 4 as an alternative to CFML for the CF Server is really not that outlandish. The pieces are all done, it's just the integration that is lacking, and CF is all about integration. Rather to rich of a project for me to undertake on my own (hence ECMAScript 2 and Rhino), and the closed nature of CF makes it rather hard as well, but for an entity like Adobe, quite a reasonable undertaking, I'd think. Here's hoping for CF9!
IMO, nested functions are a big "no no".
It's even advised against in the Core Javascript Reference:
A closure preserves the arguments and variables in all scopes it contains. Since each call provides potentially different arguments, a closure must be created for each call to the outer function. In other words, each call to outside creates a closure. For this reason, closures can use up a lot of memory. The memory can be freed only when the returned inside is no longer accessible. In this case, the closure of inside is stored in result. Since result is in the global scope, the closure will remain until the script is unloaded (in a browser, this would happen when the page containing the script is closed).
Because of this inefficiency, avoid closures whenever possible, i.e. avoid nesting functions whenever possible.
And what about cross-browser support?
Muzak,
While nothing you quote is factually incorrect, it's quite inflationary in nature. Looking at the page as a whole (from a community-edited Wiki), you see a rather fragmented collection of comments from a collection of authors with different viewpoints. To quote a rather more consistent source (the Google Maps API docs) "The Google Maps API encourages the use of function closures…".
Closures definitely have gotchas, but the same could be said for SQL databases, and no one runs away from those. You just have to know how to use them to best effect, avoid the pitfalls, and you'll be fine.
I don't understand your "cross-browser support" comment, however. All browsers support closures, and have since "forever". IE has memory leaks in certain cases, but they can be addressed without too much fuss, certainly more easily than you can address some of the rendering bugs that it has. This is also a non-issue on the server side.
"cross-browser support†as in "JS isn't cross-browser", cfml couldn't care less.
Regarding the verbosity of CFML, in your example (and I realize its just an example) the CFML version could be written in cfscript and look exactly like the JS alternative provided beneath it. The benefit of using the ML syntax vs a JS approach is when you go beyond the basics to include arg validation, type checking ect. Take for example:
Interesting post by the way!
Looks like the code snippet didn't show,
[cffunction name="doIt" output="false"]
[cfargument name="param1" required="true" type="uuid" /]
[cfargument name="param2" required="false" type="array" default="#arrayNew(1)#" /]
[/cffunction]
Justin,
The typechecking is one of the reasons I'd love to have an ECMAScript 4 (e.g. ActionScript 3) interpreter instead of a ECMAScript 3 (e.g. JavaScript) interpreter. With the 4th edition, you get typing as well as default values for parameters (and therefore optional parameters). So you could write this:
function doIt(param1:UUID, param2:Array=[]):void {
}
[...] Some say closures should be avoided because they add memory that is not clearable by the Garbage Collector. (Apparently closures are fairly complex on a low level, each storing a "snapshot" of the function's scope which can end up creating a tangle of object references.) In my tests so far this does prove true to a certain degree, but is only significant when the closures aren't cleaned up in your program code, so be sure to null out ClosurePlus references when you're done with them! Heavy use of closures (as in millions) does seem to leave a bit more un-GC'd memory clutter, but moderate use of ClosurePlus shouldn't be any worse than using the standard Event model.Here's a good discussion thread I found on the topic. [...]
Barney, you rock. I wish you every success with this project. I've been looking for a CF runtime for Javascript, and was even thinking of writing one myself. (Boy, was THAT naive!) Do you plan to continue work on this?
I'm a little astonished by all the negative reactions in the comments.
I've been a ColdFusion developer for 8 years, and I love it dearly. Yay for cfquery and pound signs. But heck yeah, I want to write my business logic in Javascript while to leveraging the CF platform!
Javascript has syntax and features that appeal to millions of developers. It is being adopted as the scripting language of choice all over the place. It has a passionate developer community, and they are continually building better libraries and more sophisticated tools.
We dig JS. And we don't care if "dynamic binding is dangerous!" or "closures make give you warts!" I'll leave the philosophy of language design to others, and continue using JS quite happily, thank you very much.
If someone wants to write a CF runtime for Ruby, Dylan, Scheme, etc… rock on! Those languages are not for me, but I'm not about to rail (no pun intended) against their allegedly dangerous language features. That's like mocking someone for using an abacus or a slide rule instead of a calculator.
One last thought: cfscript is not Javascript. Not even in CF8 with its excellent C-style syntax. The list of things you can do in Javascript and not in cfscript is ridiculously long.
David,
I implemented it more as a proof of concept than an actual for-production-use framework. However, I was surprised at how utilitarian it was, what with only the Query object and native JS functionality. Extending the set of "callbacks" to CF is fairly simple; most of the bridge work is in place, so you only have to create your JS interface and implement it with the relevant CFML. The Query one is actually quite complex because of query parameters, and it doesn't support QofQs. Something like CFLDAP or CFHTTP would be quite a bit simpler to deal with.
However, to answer your question, I don't really have an intention of making it into something "real". I just wanted to see how it'd work, both from an implementation and a usage perspective. I have this sneaking suspicion that CF9 is going to have Tamarin (the AS3/ECMAScript 4 engine that Adobe donated to Mozilla and which backs Flash Player) embedded in it, and therefore have this sort of support available in the CF server directly.
Example #1
cfset application.MySQL5_connection = MySQL5.connect("jdbc:mysql://localhost:3306/opticalalert", props)>
Example #2
cfset application.update_mailinglist_remove = application.sqlserver_connection.prepareStatement("Update mailinglist SET email = ?, why = ?, Delivery = ? WHERE email = ?")>
CF 8.X only likes prepareStatements now so.
Is this still verbose? Then its just ….
Example #3
cfset application.update_mailinglist_remove.executeQuery()>
With the new Thread scope I am sure that would work even better or a threadpool scope.