Two Guys Arguing

placeholders and currying

Posted in javascript by youngnh on 11.04.09

Wherein Javascript borrows a language feature from Arc

Today, I was going to write about Google Maps until I stumbled onto this little gem. My post still involves Google Maps code, but my focus is on Javascript and how virtually anything you can think of that might make the language more of a joy to program in, you can implement yourself.

Our post begins with a plain Google Maps application, virtually copied right from Google’s API docs example. I made a few changes though, highlighted below. I hate mixing event handlers in HTML tags and I have, as of yet, still not installed a decent mixed mode for editing Javascript inside of an HTML source file. So I pulled the embedded script into it’s own file and added listeners for load and unload events on the window.

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    <title>Google Maps JavaScript API Example</title>
    <script type="text/javascript" src="http://maps.google.com/maps?file=api&v=2&sensor=true"></script>
    <script type="text/javascript" src="maps.js"></script>
  </head>
  <body>
    <div id="map_canvas" style="width: 500px; height: 300px"></div>
  </body>
</html>
function initialize() {
    if (GBrowserIsCompatible()) {
        var map = new GMap2(document.getElementById("map_canvas"));
        map.setCenter(new GLatLng(37.4419, -122.1419), 13);
        map.setUIToDefault();
    }
}

function destroy() {
    GUnload();
}

window.addEventListener("load", initialize, false);
window.addEventListener("unload", destroy, false);

The default map is centered on Google’s headquarters in Palo Alto. Of course, Palo Alto isn’t very pleasing to look at for anyone who isn’t a smug self-centered Palo Altoan, but I suspect that most people don’t change this, at least initially, because it’s daunting to try and figure out what your hometown’s latitude and longitude is. Google Maps itself, though actually provides a decent way to turn a string location into a GLatLng, using a GeoCoderThingy. The getLatLng() method takes a callback. I live in St. Louis and my original post had to do with our lovely Forest Park, so the code below demonstrates how to center your map on America’s largest urban park.

function initialize() {
    var geolocator = new GClientGeocoder();
    geolocator.getLatLng("Forest Park, St. Louis, MO", displayMapAt);
}

function displayMapAt(center) {
    if (GBrowserIsCompatible()) {
        var map = new GMap2(document.getElementById("map_canvas"));
        map.setCenter(center, 13);
        map.setUIToDefault();
    }
}

Notice the extract parameter and method rename refactoring here. It’s good and our code is clean, but Forest Park doesn’t appear prominent enough on the map for my liking. It should be bigger, more zoomed in. Pretty easy, just extract parameter around the hardcoded zoom level, and adjust the callback a bit since now displayMapAt will take two parameters.

    geolocator.getLatLng("Forest Park, St. Louis, MO", function(center) {
	    displayMapAt(center, 14);
    });

function displayMapAt(center, zoom) {

This is ugly. Anonymous functions are powerful, but hell if I hate reading them, all curly and indented funny and pretending to be simple variables when there’s a bunch of characters there that don’t ever belong in any variable name ever.

We can use my fn.js (see Lambda Lounge, February 2009) and curry() to rewrite the callback like so:

    geolocator.getLatLng("Forest Park, St. Louis, MO", displayMapAt(14));
...
var displayMapAt = function(zoom, center) {

Much better, but we had to rearrange displayMapAt‘s parameters to get it to work and they were in a good order before, all matching the order they’re in when they’re used and such and not to mention what would happen if displayMapAt wasn’t your code and you couldn’t just mix it all up willy nilly. Haskell, which I borrowed most (all) of the ideas for fn.js from has the notion of “reversing” a function; basically creating a new function that takes the original functions arguments in reverse order. That would work here, but Javascript and Haskell’s syntactic beauty lean in different directions, so I looked to a different place for inspiration to solve this tiny, tiny problem: Arc.

Paul Graham’s hundred year language, Arc, has this placeholder syntax for anonymous functions that is ever so useful.

arc> (map [+ _ 10] ‘(1 2 3))
(11 12 13)
arc> (trues [if (odd _) (+ _ 10)]
‘(1 2 3 4 5))
(11 13 15)

So I broke into fn.js and added an identity check for the underscore variable. Once your function has all of it’s parameters, if one of them is the underscore variable (not equal to, is the reference itself) then we once again return an intermediate curried-sorta function that will on final invocation replace it’s placeholder value with a newly passed one. Right now the code only supports a single placeholder parameter, but there’s no reason you couldn’t alter the check and replace code to handle multiple placeholders.

Updated: see this Hacker News thread and Tom Robinson’s comments below for the whole story, but suffice to say === doesn’t work the way I thought it did, and _ needs a value and a simple == comparison. Also, these placeholder changes have been pushed to fn.js on github.

var _ = {};

function curry(f) {
  ...
	var placeholder;
	for(var i = 0; i < args.length; i++) {
	    if(args[i] == _) {
		placeholder = i;
		return function(replaceWith) {
		    var allargs = args.slice();
		    allargs[placeholder] = replaceWith;
		    return f.apply(null, allargs);
		};
	    }
	}
  ...
}

In the end, our displayMapAt function keeps it’s original parameter ordering, our callback isn’t an ugly inline anonymous function and the underscore very succintly points out where a variable is missing and that a future call will replace it with a real value.

    geolocator.getLatLng("Forest Park, St. Louis, MO", displayMapAt(_, 14));

var displayMapAt = function(center, zoom) {

fin.

About these ads

6 Responses

Subscribe to comments with RSS.

  1. Tom Robinson said, on 11.05.09 at 12:21 pm

    You should probably assign an object to the underscore, otherwise any argument which is undefined will match:

    js> var _
    js> _ == undefined
    true

    Just doing “var _ = {}” should be sufficient.

    • benjaminplee said, on 11.05.09 at 12:36 pm

      I too was wondering about that.

      I am still trying to wrap my head around curring and a few other Functional Programming concepts. Functions as first class citizens which can be passed and invoked in different contexts is easy to understand and follow. Transforming functions and returning new ones is a bit harder to follow given my OO background. Good post though.

      Also, presumably, a language that supported named method parameters would allow you to not worry about order and know what params have not yet been defined.

      • youngnh said, on 11.05.09 at 12:52 pm

        so this isn’t a value thing.

        functions could expect an empty object to be passed to them and if I just looked for an empty object, I might mistake a real value for a placeholder.

        I want to know if the function has been given the _ variable itself. I’m interested in it’s reference.

        if you’ll notice, I made a === check instead of a == equals check, as the former is stricter, only returning true when you compare an object to itself, and the latter returning true when two objects have the same value.

    • Javascript geek said, on 11.05.09 at 1:58 pm

      not only that but:
      js> var _,x
      js> _ === x
      true

      var _ = {} is much better, also why is this not in fn.js on github?

  2. Karl said, on 11.05.09 at 1:04 pm

    See Oliver Steele’s partial() in his functional library for the exact same functionality. :)

    I’ve also seen this done with undefined as the test with the advantage of namespace cleanliness and the disadvantage of having to type undefined.

  3. klaush said, on 03.10.10 at 10:00 am

    the curry functions only works for simple args, imagin
    func( {animate:false,load:true,icon:false},html){
    dosomething
    }

    currying this function destroy the object, so you will need to add an object/array parser aswell


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.