AaronCrane.co.uk

Math.max in JavaScript

JavaScript is a fine language in many ways — a fact that many people have noticed by now. But one of its biggest problems is the design of its standard library. There’s a lot of low-hanging fruit to complain about; this note covers just Math.max.

Math.max in JavaScript does essentially what you’d expect: it calculates the maximum of several numeric values. It also does something very sensible if you give it no values at all: it returns -Infinity.

If you need to use Math.max, you will be in one of two situations: you might have several numeric variables, or you might have an arbitrarily-long array of numeric values.

In some languages, like Perl 5, the distinction isn’t meaningful:

use List::Util 'max';

my $max_of_array     = max(@array);
my $max_of_variables = max($x, $y, $z);

# For that matter, you can use any combination:
my $max_of_all_sorts = max($x, @array, $y, @array2, $z);

But in JavaScript, that approach isn’t possible; the API has to pick one form or the other as the “native” approach, and force the other to be implemented in terms of it. So when designing a standard library for a JavaScript-like language, which approach should you pick?

The obvious way of tackling that question is to look at the consequences of each decision. Suppose the array form were considered the “native” approach. Then you’d have code like this:

var max_of_array     = Math.max(array);
var max_of_variables = Math.max([x, y, z]);

You could also consider making max a method on an array object, instead of a pseudo-static method called on its namespace object:

var max_of_array     = array.max();
var max_of_variables = [x, y, z].max();

That seems particularly concise and obvious, but even using the spurious Math namespace, it’s easy to call max on either an array or a list of named variables. And of course, that’s because building an array from a list of named variables is trivial in terms of both cognitive and syntactic weight.

The other API approach — making the multiple-variables form the “native” one — yields straightforward code when that’s what you’re trying to do:

var max_of_variables = Math.max(x, y, z);

But if you have an array of values, the best you can do is this monstrosity:

var max_of_array     = Math.max.apply(Math, array);

Not only do you have to explicitly invoke apply on Math.max, you also have to redundantly say again that max belongs to Math. By any reasonable standard, this approach yields a worse API than the other one.

So, why does JavaScript use this worse API?

It’s obviously way too late to change JavaScript’s standard library now. And I’m aware that the difference here is of limited impact — It’s not absolutely terrible that JavaScript programmers have to jump through this hoop to find the maximum of an array of numbers. But that doesn’t justify use of a suboptimal API design in something as widely used as a language’s standard library. The argument’s somewhat trite, but if an improved API could save one second for each of a million people, that’d be a big win.

I’ll also point out that, since JavaScript is designed for use on the web, excessively-verbose APIs hurt the end user as well as the programmer — the user effectively has to download the source code.

Depressingly enough, though, the situation as presented above isn’t actually as bad as it gets. The first browser to have JavaScript was Netscape Navigator 2.0. (Unsurprisingly, given that the language was developed in-house at Netscape.) Array literals first made an appearance in JavaScript 1.2, which came with Navigator 4.0. In the absence of array literals, using an array-oriented max API for a list of variables becomes rather more awkward:

// Convenient form, as above:
var max = Math.max([x, y, z]);

// Or without array literals, ugh:
var max = Math.max(new Array(x, y, z));

Worse even than that, though, is the fact that before ECMAScript edition 3, the Math.max function didn’t calculate the maximum of an arbitrary number of arguments. Instead, it took exactly two arguments, and returned the greater. That is, JavaScript’s mid-1990s API designers not only failed to find the better of the two obvious choices, they even failed to find the worse one. D’oh.