Wednesday, November 13, 2013

JavaScript does not have "pass by reference"

About every 17 minutes, somebody on Stackoverflow answers a question or posts a comment that implies that passing an object reference to a JavaScript function is an example of "pass by reference". Those people are all wrong, so I am typing in this post so that I can link to it and save myself some time.

These wrong people are not bad people, and they're almost always trying to answer a question and be helpful. The problem here is that "pass by reference" has a very clear meaning when discussing programming language semantics. What it doesn't mean is that it's possible to pass a reference to a modifiable object into a function. Yes, you can do that in JavaScript (and Java and lots of other languages), but that's not what "pass by reference" means. The key here is the super-important word by in that phrase.

Here's the simple test to see if a language supports "pass by reference":
Can you write a function "swap" in Language X that looks and does something like:

variable x, y;
x = 1;
y = 2;
swap(x, y);
print(x + ", " + y); // prints "2, 1"

If the answer is "yes", then congratulations, your language supports "pass by reference". C++ is one of those. You'd write "swap" something like this*1:


void swap(int &x, int &y) {
  int tmp;
  tmp = x;
  x = y;
  y = tmp;
}

The "&" thing in C++ tells the compiler that instead of passing in the value of parameters when calling the function, it should pass a reference to the parameters. Now, because (in C++, at least as far as I remember) you can only get a reference to something like a variable, and not something like the constant "14", that means you get an error if you do

swap(14, y + 1);


Note also that this works in C++ because the compiler knows about the nature of the called function; it has to be able to see a declaration somewhere for things to work.

Languages like JavaScript have no such luxury. JavaScript has no idea whatsoever what any function actually looks like. References to functions are determined during the process of expression evaluation, and all the runtime knows (modulo whatever sorts of smarty-man optimizations are going on) is that it's got a reference to a function and a list of argument expressions to evaluate. There's no such thing (except in the perverse case of the "arguments" monster in functions, which is a whole 'nuther thing) as a reference to a variable (or a computed value, of course) in JavaScript.

JavaScript is always and only a "pass by value" language. When you pass a reference to a mutable object, you're passing a value — the reference to the object. You can't write "swap" in JavaScript.

Algol 60*2 had a weird parameter passing variant called "pass by name". The idea was that an expression in the calling environment would be treated as something to do whenever the formal parameter in the function was referenced in an expression. A JavaScript version of that would be that you'd pass in a function, and the called function would invoke that passed-in function whenever it wanted the value:


function passByName( paramFunction ) {
  if (paramFunction() == 2) {
    somethingSomething( paramFunction() );
  }
  else {
    whatever( paramFunction() - 1 );
  }
}


So in Algol you'd write something like:

  passByName( foo + 17 );

and the compiler would do something more-or-less like wrapping that expression up in a function, and in the called function references to the parameter name would result in calls to that generated function. Those things were called "thunks" as far as I know.

So: yes, it's true that in JavaScript it's possible to use an object with properties to make it possible for functions to change stuff in the calling environment other than via a simple return value. That's not "pass by reference".

*1 - I barely remember C++. It's something like that.
*2 - I don't know anything about Algol 60 other than what some dude told me in college 30 years ago. The "pass by name" concept is cool anyway and it would probably work something like that. It's kind-of like Haskell lazy evaluation in some ways, I think. Maybe.