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.

Follow

Get every new post delivered to your Inbox.