In many ways, call-by-sharing is the most intuitive evaluation strategy. It captures the semantics where the caller of the function shares objects with the function being called. The called function can mutate those objects so long as the objects themselves are mutable. Because the objects are shared, any changes will be reflected in the calling function. This convention is applied to all objects being passed.
For immutable objects, call-by-sharing is indistiguishable from call-by-value. Indeed, the key to functional programming is that aliasing (the result of sharing objects instead of copying them) is always 100% safe. This is achievable by forbidding mutation. A class of powerful functional data structures exist that exploit this efficiency. These data structures can usually be efficiently updated without destroying the original copy, because the update aliases large parts of the original data structure instead of mutating it.
whereas the following does not mutate the number:
In the first example, the syntax
A = 0 is a mutation of the array. The
array does not because a different object; it is the same object as before, but
its first element is changed. In the second example, the syntax
n = 0 is very
different. It does not mean “change the value of this number to
it means “rebind the name
n to the new value
0”. This subtlety is the source
of much confusion. In languages with call-by-sharing semantics, names are often
distinguished from objects. Names are bound to objects, and a name can be
rebound to a different object using an assignment keyword. Assignments do not
affect the object the name was originally bound to.
Indeed, if we had written our first example as
then we would have seen a similar result as our second example. Here we are no
longer mutating the array; we are now binding the name
A to a new array.
There is a wealth of resources online that claim that Java is call-by-value, or that Ruby is call-by-reference. These languages are both call-by-sharing. So are these resources wrong? In fact, the former is not wrong. The latter is a little bit of a stretch, but there is some meaning in the nonsense. Rather, this exposes some nuance about what exactly is a value, and what exactly is a reference.
Java is call-by-value
In Java 1.8, there are exactly 9 user-visible value types:
Object. The first 8
of these are known as the primitive types. Each of these types can be
represented as a machine integer of various fixed sizes. All the primitives
represent themselves, but
Objects are more interesting.
Objects can be
thought of as effectively pointers to another memory location. That means that
Objects are just numbers, like
longs are. However, these numbers do not
contain the user-facing substance of an
Object, but rather the location of the
user-facing substance. They are still values deep down—but their values are not
directly exposed to the user.
This understanding of what an
Object really is helps to understand much of the
behaviour of objects. For instance, even if two
Objects have identical
internal structure (like, say, two
Strings that contain the same contents),
they might not test
==! This is a common “gotcha” for new Java programmers,
who try writing
The conditional here, to their confoundment, can be false even if
"Hello World". Why is this? Because even if the two
have identical user-facing structure, they might be stored in different
locations in memory, and hence not have the same value. And in Java, the
comparison operator compares the values of
Objects, as it does with any
primitive type. This is why Java programmers need to instead using the
.equals() method to compare the substance of
Now it might be clear why Java programmers often call their language
“call-by-value”: because it is! However, from a programmers perspective,
Objects as memory addresses is often not helpful. If I have an
Object that represents a complex number, its value—as far as I’m concerned—is
the number itself, not the memory address where the number is stored. Although
“call-by-value” is accurate from a low-level viewpoint, it is more helpful for
the programmer to think in terms of
Objects’ substance than in terms of
pointers. And when we think of
Objects’ and primitives in terms of their
user-visible behaviour—in which case primitives behave quite similarly to
Objects—it leads to the inevitable conclusion that Java is indeed
Ruby can look like call-by-reference
To call Java’s behaviour call-by-value is defensible. In strictest terms, to call Ruby’s behaviour call-by-reference is not.
Indeed, Ruby has the same semantics as Java:
At this point it is clear that Ruby is not call-by-reference. This is clear if we consider the equivalent C++ code, which is call-by-reference:
But why then do so many sources claim that it is? They are wrong, but there is meaning in their madness. Firstly, there is no concept of a primitive type, as with Java. All objects are first-class objects and have similar behaviour. Indeed, in the most simple implementations, all objects are internally stored as what are effectively pointers. Since all objects are references, therefore all calls pass some kind of reference.
Further confounding the situation is that Ruby has no immutable types. Rather, immutability is a property of the instances. Some types are practically immutable; there is no real standard way to mutate a numeric type, for example. But theoretically, the semantics permit mutation of all unfrozen instances. This makes calling the behaviour call-by-value more confusing than it is illuminating.
Semantics vs. Implementation
Above I had explained how various languages have call-by-sharing semantics. Sharing, it would seem, implies that the objects being passed are located in the same memory location. This idea is false.
Indeed, one implementation of sharing is to place two objects in the same memory location. Then, any mutation on one object would mutate the other.
But another, equally valid, implementation is to place two objects in different memory locations, but to proxy mutations on one object to the other. This may sound stupid—why do extra work, after all? But the user is none the wiser. The semantics of both implementations are identical.
Many languages that call-by-sharing do not necessarily share some things at all. Java does not share its primitive types; those are immutable, so copying them is the same as sharing them from the user’s perspective. And copying a number provides much faster access than copying a pointer to a number allocated on the heap somewhere.
In summary, memory does not need to be actually shared by a particular implementation for a language to have call-by-sharing semantics.
I hope this blog post has cleared up some of the confusion regarding call-by-value and call-by-reference, and how call-by-sharing—a relatively unknown term—is indeed more precise and accurate for many languages.