Saturday, August 5th, 2006

Can Your Programming Language Do This? Javascript Can.

Category: JavaScript

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.

javascript

  1. function Cook( i1, i2, f )
  2.     {
  3.         alert("get the " + i1);
  4.         f(i1);
  5.         f(i2);
  6.     }
  7.  
  8.     Cook( "lobster", "water", PutInPot );
  9.     Cook( "chicken", "coconut", BoomBoom );

And if you want, you can replace those function references and create a function on the fly.

javascript

  1. Cook( "lobster",
  2.           "water",
  3.           function(x) { alert("pot " + x); }  );
  4.     Cook( "chicken",
  5.           "coconut",
  6.           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.

javascript

  1. function map(fn, a)
  2.     {
  3.         for (i = 0; i < a.length; i++)
  4.         {
  5.             a[i] = fn(a[i]);
  6.         }
  7.     }

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.

Posted by Michael Mahemoff at 5:44 pm
17 Comments

+++--
3.9 rating from 32 votes

17 Comments »

Comments feed TrackBack URI

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.

Comment by Simon Willison — August 5, 2006

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.

Comment by Steve Bjorg — August 5, 2006


def cook(i1, i2, f):
    print 'get the', i1
    f(i1)
    f(i2)

    cook('lobster', 'water', PutInPot)
    cook('chicken', 'coconut', BoomBoom)

Comment by Min-hee Hong — August 5, 2006


(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)

Comment by John Leavitt — August 5, 2006

[…] Pues yo no lo sabía, y lo supe gracias a Ajaxian. […]

Pingback by ¿Ud. sabía que… at Gadmonks — August 6, 2006

> 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.

Comment by Masklinn — August 6, 2006

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.

Comment by Troels — August 6, 2006

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.

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

Comment by Gareth — August 6, 2006

Nice. Now what’s javascript’s answer for packaging and import?

Comment by Bill de hOra — August 6, 2006

Anonymous function syntax (what you term “creating functions on the fly”) certainly doesn’t cause any performance issues

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.

Comment by Simon Lodal — August 6, 2006

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

Comment by Steven Elliott — August 6, 2006

…..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

Comment by Steven Elliott — August 6, 2006

….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

Comment by Steven Elliott — August 6, 2006

@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

Comment by Valery Silaev — August 7, 2006

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).

Comment by CW — August 7, 2006

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…)

Comment by Stephen Kellett — August 7, 2006

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…

  1. function AnonymousDebug()
  2. {
  3.     //place debugger breakpoint here
  4. }
  5.  
  6. //...pseudo code...
  7. myObj.doSomething("foo", function(something)
  8. {
  9.      //for debugging purposes, make call to globally scoped named function
  10.     AnonymousDebug();
  11.  
  12.     //...now do something, and if I have a breakpoint set in the AnonymousDebug function I can now step through all this code
  13. });
Comment by Ryan Gahl — August 7, 2006

Leave a comment

You must be logged in to post a comment.