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.

17 comments:

  1. Thanks for the post, it helped clear my head a little on JS semantics. The "pass by reference" semantic can be traced back through C# as least as far as Pascal with its "var" parameters. The C# guys are used to the very clean "ref" and "var" style parameters, which are easy to use and understand. C++ references are different...

    tldr; C++ only supports pass by value.

    The "C" family including C++ passes **everything** by value. They have no "pass by reference" semantics. A C++ reference parameter is a value parameter. It can be thought of in simple (but incorrect) terms as a const * to an underlying type. A C++ reference cannot be modified (to refer to something else) once bound. However, the contents of a referenced object may be modified subject to the constness of individual members. The standard explains all this in rather specific but opaque terms (It's a joy to read.. not!)

    Unfortunately there is no semantic equivalent (not sure about C++11) to C#'s "ref" and "var" parameters. You can pass a pointer to an object, but there's no guarantee (by design) that's been instantiated.

    Postscript. C++ like all the C family is very stack-centric. Value-centric parameters fit well with a deterministic, stack based view of the universe since the values are often optimised away in CPU registers and have predictable lives. Basically it makes sense on real machines. This deterministic, stack-based concept either doesn't exist or is much less prevalent in virtualised environments like the JVM, CLR, and ECMAScript.

    ReplyDelete
    Replies
    1. Hi. I'm no longer a C or C++ programmer, so I really don't care much about C++ terminology. However, the key fact is that it's *possible* to create a reference to a variable. That's just not possible in languages like JavaScript, Java, Clojure, Erlang, Python, or Ruby. Yes, C++ passes a value, but the fact that in the *calling* environment an expression that looks like an r-value variable reference is converted to a pointer to the implementation of that variable is a key difference.

      So yes, C++ only supports pass-by-value, but it also supports the *implicit* generation of a reference without anything explicit happening in the calling environment. The effect is solely a result of the nature of the called function, in other words. That seems an awful lot like how pass-by-reference would have to work in *any* language, at least any language that's anything like C or C++, but if you don't want to call that "pass-by-reference" it's OK with me.

      Delete
  2. Thanks for the post. And thanks for all the clarification posts in stackoverflow.
    Now I've sth. that looks like a "capture by reference" (what should be impossible with your theory):

    var result = [];
    for (var i=0; i < 5; i++) {
    result.push(function () { return i }); // (*)
    }
    console.log(result[1]()); // 5 (not 1)
    console.log(result[3]()); // 5 (not 3)

    // code from http://www.2ality.com/2013/06/basic-javascript.html

    I though the function captures the number i by value. Which means it has a pointer address to the memory "space" which contains 0. Now in JS primitive datatypes are immutable and the increment of i (i++ or i=i+1) "assigns" a new pointer address (containing 1) to i BUT its impossible (in my opinion) that the i inside the function now contains the pointer adress, which contains 1. Its only possible to "re-point" inside the function if its captured by reference (or in C by a double pointer). So how this is solved in javascript? For me, it looks like some interpreter magic rules.

    ReplyDelete
    Replies
    1. The issue here has absolutely nothing to do with parameter passing semantics. All those functions are returning the *current* value of the variable "i" from the outer scope. At the end of the loop, the value of "i" is 5. This is about how closures work in JavaScript, not how parameter passing works. The only parameter involved here is the function that's instantiated on each iteration. Each of those functions just has a simple reference to that outer variable "i". If you didn't want that effect, you'd create another function and pass "i" into that to create a copy in a new lexical scope, and then have that function return the function to be added to the "result" array.

      If you ask this question on StackOverflow I'll look for it :)

      Delete
    2. Thanks. I wrote a similar question here: http://stackoverflow.com/questions/20830362/javascript-does-no-capturing?noredirect=1#comment31240206_20830362
      Still nobody could answer if javascript does capturing or not. Sorry thats not the topic of you post here...so you (of course) delete my posts.
      thanks again for your help.

      Delete
  3. This comment has been removed by the author.

    ReplyDelete
  4. It seems JavaScript does have pass-by-reference, check out this example of sorting on an array, (had to paste it on pastebin, unable to paste code here)

    http://pastebin.com/3GABDHrm


    OUTPUT:
    Before selectionSort: 45,12,23,89,78
    Inside selectionSort: 12,23,45,78,89
    After selectionSort: 12,23,45,78,89

    ReplyDelete
    Replies
    1. Read the article more carefully. Your code is passing a value to the "sort" function, and that value is a reference to the array "arr". Yes, the function can modify the array via that reference, which is exactly what I said in the article. However, what that function *cannot* do is modify the value of the variable "arr".

      Delete
  5. This comment has been removed by a blog administrator.

    ReplyDelete
  6. This comment has been removed by a blog administrator.

    ReplyDelete
  7. This comment has been removed by a blog administrator.

    ReplyDelete
  8. What about computational complexity? Passing a 1,000,000 element array by value takes (approximately) 1 million times longer than passing a 1,000,000 element array by reference. At the end of the day the issue is really whether all the raw data have been passed to the function (e.g., by value), or whether a pointer, enclosing data structure, or other reference has been passed instead.

    ReplyDelete
  9. Yes true , like in java. , JavaScript , only does pass by value , the confusion is created when reference types are passed to a function. Say like an array ref variable ,which like in java is also object

    ReplyDelete
  10. Wow, to my mind, to say that java and javascript don't support pass by reference is a very strange way of looking at it. (The author is not a *bad* person for looking at it that way...) The fact is that you put an object or an array in a function call in javascript or java, what gets passed to the function is a reference to that object or array. To me, that's "pass by reference". Therefore say that the language does not support "pass by reference" is incorrect. I think that what you are trying to say is that some languages (like javascript and java) do not provide a way to create a reference (pointer) to scalar variables.

    ReplyDelete
    Replies
    1. I don't think yours is a good way to look at it thou.
      When you pass in an object, that in itself IS a reference. You are passing in a reference by value. The most obvious telltale is that while you can modify the objects you pass in, you cannot initialize it to something else, or swap it with another object.

      Going back to C++ for an example, we can have a swap like
      void swap(int *p1, int *p2){
      int temp = *p2;
      *p2 = *p1;
      *p1 = temp;
      }
      int a=1, b=2;
      int *x=&a, *y=&b;
      swap(x,y);
      std::cout<<*x<<','<<*y<<'\n';//will print 2,1

      but I doubt you will find ANYBODY who thinks that's passing by reference. That's just passing in reference by value. The swap function will break if it looks like
      void swap(int* p1, int* p2){
      int* temp = p1;
      p1=p2;
      p2=temp;
      }
      One way to make it not break is to simply pass all our references in *by reference* (so void swap(int*& p1, int*& p2)). This is what javascript cannot do.

      Delete
  11. This comment has been removed by the author.

    ReplyDelete
  12. This comment has been removed by the author.

    ReplyDelete