Saturday, August 5th, 2006
Can Your Programming Language Do This? Javascript Can.
Joel Spolsky illustrates the power of first-class functions in "Can Your Programming Language Do This?". The article explains how Fortran, C, and Java fall short of fully supporting first-class functions in their own respective ways. What's interesting from our Ajax-tinted perspective is that he uses Javascript to illustrate these concepts. In other words, although he doesn't explicitly state it, Javascript is an example of a language that does support first-class functions.
In the simplest case, you can pass a function reference around.
-
-
function Cook( i1, i2, f )
-
{
-
alert("get the " + i1);
-
f(i1);
-
f(i2);
-
}
-
-
Cook( "lobster", "water", PutInPot );
-
Cook( "chicken", "coconut", BoomBoom );
-
And if you want, you can replace those function references and create a function on the fly.
-
-
Cook( "lobster",
-
"water",
-
function(x) { alert("pot " + x); } );
-
Cook( "chicken",
-
"coconut",
-
function(x) { alert("boom " + x); } );
-
And then we can create generic library functions, like looping (or sorting for that matter) which rely on functions being passed in.
-
-
function map(fn, a)
-
{
-
for (i = 0; i <a.length; i++)
-
{
-
a[i] = fn(a[i]);
-
}
-
}
-
Hmmm ... That looks familiar ;-).
You probably wouldn't have seen Javascript used in a generic programming article like this a couple of years ago! One real-world caveat applies with these examples: creating functions on the fly is often great for code readability, but it does cause a performance hit and also increases the potential for a memory leak.












Anonymous function syntax (what you term “creating functions on the fly”) certainly doesn’t cause any performance issues - it’s just an alternative syntax to regular named functions. The only time it would cause performance issues is if you were creating a new function using a Function constructor because that involves evaling strings.
As for memory leaks, as long as you’re not assigning any references involving DOM elements you’ll be fine. If you are, you need to have a proper understanding of what’s going on or you could run in to problems.
Closures (a.k.a. anonymous first-class functions) are supported since C# 2.0. With the additon of type-safety, it makes C# the perfect language for writing flexible Web 2.0 services. That’s why chose it for platform.
Closures are an important part of modern software engineering and really a great tool in a developer’s toolbox.
def cook(i1, i2, f):
print 'get the', i1
f(i1)
f(i2)
cook('lobster', 'water', PutInPot)
cook('chicken', 'coconut', BoomBoom)
(defun cook (i1 i2 f)
(format t "get the ~S~%" i1)
(apply f i1)
(apply f i2)
)
(cook 'lobster 'water 'PutinPut)
(cook 'chicken 'coconut 'BoomBoom)
[…] Pues yo no lo sabía, y lo supe gracias a Ajaxian. […]
> As for memory leaks, as long as you’re not assigning any references involving DOM elements you’ll be fine.
> If you are, you need to have a proper understanding of what’s going on or you could run in to problems.
And only MSIE really has issues with that, too.
Oh, and by the way Joel’s MAP function is completely un-functional as it works through side-effects, modifying the input array parameter which is a huge no-no in FP.
> Closures (a.k.a. anonymous first-class functions) are supported since C# 2.0.
Closures and anonymous first-class functions are different beasts, and while closures are often implemented via anonymous first-class functions they’re different concepts.
For example you could use anonymous first-class function in a factory-type pattern, returning different functions based on it’s input. And you don’t need closures to do that.
What John Leavitt tries to communicate with the above comment, is that lisp is also a function-oriented language. (And even more so than js).
However - what’s interesting with js is that it’s the first functional language to break through on a large scale - partially because it’s a hybrid. That it’s used in a book on function programming further consolidates this position. That’s indeed interesting - thanks Michael.
Well, apart from the fact that if you create anonymous functions in a loop for example, you get x copies of the function object. With a named function, you get one function object with x references to it - much more efficient
Nice. Now what’s javascript’s answer for packaging and import?
Yes, it might cause a performance hit. Anonymous functions are often closures, like in the second example:
Cook("lobster", "water", function(x) { alert("pot " + x); } );
When this is executed inside a function, the inline function becomes a closure. When it is executed by Cook(), symbol references inside the inline functions are looked up in 3 steps:
1) Local scope; local variables and arguments.
2) Closure scope.
3) Global scope.
#2 is the mythical performance hit. The global scope is just the outermost closure that all functions have. But closures can be chained (with the global scope always being last, and of course there could be more closure levels), which is the case here. Each extra level in the chain causes a performance hit in symbol lookup.
You might avoid the hit if the inline function only references local variables.
The performance hit has always been mentioned but not explained in Netscape docs, also related to ‘with’ statements, but I believe it is overstated. Hash lookups are extremely fast and should not cost much compared to all the other things a script interpreter does.
I use this for includes. Once loaded you can either load dependencies from the page or from a .js file as in:
include(”/admin/js/org/tool-man/core.js”,null);
Has been tested with FF and IE 6.+
No scope but and little management but that all could be added.
best,
Steven
/**
* First thing to load. The ablitiy to load dependencies in a js file
* @param file - path if relative must be relative from where the "document" is being
* loaded. This would be relative to the jsp or html file and not relative
* to the .js file. For instance if the file being loaded is:
* www.yourserver.com/page.html
* And in page.html you were loading js files in the HEAD with a relative SRC of
* scripts/my.js and my.js included("myOther.js") , myOther.js would be relative to page.html and NOT
* my.js.
* An easier way is to simply make the include(path) absolute from the root of the application.
*/
function include(file, id)
{
var s = document.createElement("SCRIPT");
//Make sure that the file address is absolute
var ln = document.location.pathname.lastIndexOf("/")+1;
var absPath = document.location.pathname.substr(0,ln)
if( file.substr(0,1) != "/" )
{
file = absPath+file;
}
s.src = file;
s.type='text/javascript';
s.language="javascript";
if( id != null )
s.id = id;
var head = document.getElementsByTagName('head')[0];
//Only want to add the file once even though it may be included in various places.
var scriptPath = "";
for( var i = 0; i
…..the rest
//Only want to add the file once even though it may be included in various places.
var scriptPath = “”;
for( var i = 0; i
….for some reason the rest of the code won’t post even wrapped in code block. If you want the rest please email me at $lastname at interactivetec dot com.
Steven
@Masklinn / August 6, 2006
Oh, and by the way Joel’s MAP function is completely un-functional as it works through side-effects, modifying the input array parameter which is a huge no-no in FP.
Yes, “map” with side-effects must be named “map!” :))
VS
This is really nothing special. Yes some languages doesn’t support it, but that doesn’t mean the same thing cannot be achieved. With the use of some simple design patterns such as the Strategy Pattern, even java can do it (abeit being quite very cumbersome, but comes with much more stability and safety).
Totally disagree about the “more readable” bit. I hate seeing fucntions defined on the fly. Sloppy and lazy are what come to mind when I see this.
Quite apart from the mess you get in a profiling tool because the function is anonymous rather than named. You need to think bigger picture - maintenance and debugging support. If you don’t know the name of the function (and you often won’t have a filename or line number either for an anonymous function) you run into a lot of trouble if you should need to use the debugger or a complementary software tool (profiler, memory leak debugger, flow tracer…)
To overcome the anonymous function debugging problem you can use an AOP call dispatch layer to inject debug statements (which would be a good idea even without anonymous functions), or simply hard code in your debug calls to a known named globally scoped function, which you can then use your debugger to attach a breakpoint in and then step through into the anonymous code…
[code]
function AnonymousDebug()
{
//place debugger breakpoint here
}
//…pseudo code…
myObj.doSomething(”foo”, function(something)
{
//for debugging purposes, make call to globally scoped named function
AnonymousDebug();
//…now do something, and if I have a breakpoint set in the AnonymousDebug function I can now step through all this code
});
[/code]