8

I was reading a proposal for value types in Java, and I came across this sentence: "Object identity serves only to support mutability, where an object’s state can be mutated but remains the same intrinsic object."

From what I understand (albeit tentatively), object identity is the idea of your variable acting as a pointer or reference to an object located elsewhere in memory (such as objects instantiated on the heap in Java or C#). So what would this have to do with object mutability? Does this imply that, for example, instantiated objects on the stack in C++ are immutable? I'm having trouble seeing the link here.

  • "Does this imply that for example, instantiated objects on the stack in C++ are immutable" I think what makes an object immutable is how the object is designed and what it allows, not for where it is instantiated or its identity. What do you think? – jordan May 4 '14 at 20:22
  • Is there a discussion forum associated with that proposal? I would suggest that a value type should be regarded as a bunch of variables bound together with duct tape. Given struct Point {public int x,y;}, a declaration Point pt should effectively declare two variables called pt.x and pt.y. A method parameter of type Point should be two int parameters. Under such rules, the only aspects which would require any change in the Runtime would be arrays and function return values. Arrays containing just primitives or just objects could be handled... – supercat May 5 '14 at 1:43
  • ...by using primitive arrays or object arrays and interleaving elements. Arrays containing a mix of primitive and values could be accommodated as an array of objects, the first item of which was a reference to an array of values. If the number of objects or primitives in a structure was limited, function returns could be handled without changes to the runtime, by adding members to Thread to hold them. – supercat May 5 '14 at 1:48
  • 1
    @supercat the way the JCP works, if someone wants it in the core language and scream loudly enough that "Java is dead if it doesn't get this" it gets included. – jwenting May 9 '14 at 7:06
  • You may find this article helpful, about this very subject: Objects Should Be Immutable – yegor256 Jul 22 '15 at 2:01
9

Before tackling identity, let's define what we mean by equality a little more precisely. We say two things are equal if and only if we can't tell them apart (see: Identity of indiscernibles). That means that whether two things are equal or not depends on the means we have to inspect them.

Let's think about that some more in programming terms. Let's leave our preconceptions at the door and suppose we're working in a brand-new unknown language where all variables and values are immutable. By the definition above, two values A and B are equal if and only if there are NO programs in the language that yield different results when A is used in place of B or vice-versa. Let's say A and B are (IEEE 754) floats, and when substituted into the expression _ + 1.0, the result is 1.0 for both A and B. Surely A and B are both zero. Are they equal? That depends - does the language provide any function that allows me to determine the sign of the zero? If it doesn't, they're equal; if it does, they may not be.

So two values are equal any time they give the same results for all possible combinations of operations they support. Immutable values in particular don't produce different results depending on which operations were previously applied to them. For that reason, we don't care if two variables point to two copies of the same value or if they both point to the same copy.

What does this have to do with mutability? Mutability implies our language has some notion of a memory cell whose contents can be overwritten. Let's say we add support for mutable memory cells to our language:

  • ref <value> creates a new memory cell, distinct from all others, initialized to <value>.
  • <variable> := <value> overwrites the contents of a reference cell.
  • !<variable> returns the value currently stored in a reference cell.

Now let's think about what equality means for memory cells. Suppose A = ref 0 and B = A. Consider this program:

A := 1
print(!_)

Substituting the blank for A prints 1, and substituting for B prints 1 as well. Now suppose A = ref 0 and B = ref 0. In this case, substituting into the above program prints 1 and 0, since now A and B point to distinct memory cells.

So it does matter to us whether two references point to the same memory cell or different memory cells. Since that matters, it'd be useful to have an efficient and general way of telling two references apart. Our current method of comparing the values they hold, and if they're equal mutating one of them is troublesome for a number of reasons:

  • It depends on being able to compare the values stored in the memory cells for equality. Equality doesn't make sense for all types - for example, it's generally meaningless for functions, because there's no general method to determine if two unknown functions are equal (this is venturing into Halting Problem territory). So given two references to memory cells storing functions, we can't compare the functions they hold for equality.
  • It depends on having some value that we can assign to one of the two references. So even if equality made sense for all types in the language, we still need access to a value for each type we want to compare. What if constructing a value of that type has side effects?
  • The reference value we use to mutate one of the references must be different from the value the memory cell already has, so we actually need two values.
  • The code to compare references of different types will look exactly the same save for the two values we use.
  • We need to back up and restore the value of the reference we mutate to avoid changing the meaning of the program.

So it'd be useful for the language to provide an operation to directly check if two references point to the same mutable memory cell. Such a function is pointless for immutable values; in fact, I'd say it's downright harmful. If there existed a way to tell if two 1s are stored in different places in memory, then there can be programs that care whether I pass one 1 or the other. I really don't want to worry about whether I have "the right 1"; math is hard enough as it is! So it's clear that being able to check for memory equality is mainly useful for mutable types.

5

Image you have two objects for example to instances of the class List. Both lists have the same content. Now you are reading the lists and calculate and output something based on their content. Does it matter what list you use? It doesn't because they have the same content.

But what if one of the lists is changed? Now it does matter which one you choose for reading and outputing something. Therefore you need to be able to distinguish between them and you want even in some situations be able to check if two variables point to the same object (or here list).

If you however are not able to change the content of a list then you don't even need to have two list objects with the same content because you can't change them anyway. If you want to "change" the content you will instead create a new list object with different content - which is as i said a new object. So from this point of view the identity doesn't matter. Only the content does and only the content should be used for comparing two lists.

Also note that the compiler could point to the same list object even if you declare two objects because it only needs to store its content once as it cannot change.

0

Object identity does not exist "only" to support mutability, but has other uses as well [indeed, an instance of Object type is useful only as an identity token, since it has no observably-mutable attributes whatsoever!] Instead, identity is an often-unwanted characteristic that a mutable object will acquire any time there exist multiple references to the object, they are not all controlled by a single owner, and entities outside the owner might--without the owner's knowledge--make or see changes to that object.

Suppose that some static field X holds a reference to single-element instance of int[], and X[0]=5. Static field Y also holds an element to a single-element instance of int[], and Y[0]=5. If code ever calls IdentityHashCase() on X and Y, or if code tests X==Y, it will be able to discern whether X and Y identify the same instance of int[] or different instances. Likewise, if code does something like X[0]+=1; Y[0]+=2;, the behavior will depend upon whether X and Y identify the same instance or different instances. If, however, code never tests X and Y explicitly for reference equality, nor checks their identity hash code, or does anything else "reference-related", and if X[0] and Y[0] are never modified, then X and Y will be equivalent regardless of whether they identify the same arrays or different arrays.

One of the nasty things about identity in Java or .NET is that the identity of an object fundamentally depends upon the whereabouts of every entity in the universe that might make or see changes to that object. If a reference to an object is ever freely exposed to outside code, the owner of the object will lose control over it and never be able to get it back.

Value types, unlike objects, can never be observed or modified except by the object or method wherein they are declared. Thus, an object which holds a value-type field doesn't have to worry about losing control over it, since it is semantically impossible for that to happen. In .NET (though not Java) it's possible to pass a variable, field, array element, or other storage location as a "reference parameter". Doing so will temporarily allow the method to observe or modify the contents of the passed storage location for the duration of its execution, but any passed "byref" (the technical term for what is passed) is guaranteed to be destroyed before the method returns, thus ensuring that the caller maintains control over the data held therein, and ensuring that the container for that data won't acquire an unwanted identity.

  • In Java, Object has a very important mutable attribute, the associated mutual exclusion! – Jan Hudec Jun 2 '14 at 14:39
  • @JanHudec: I would say monitor locks in Java and .NET as use Object as an identity token. To the extent that they are viewed as encapsulating mutable state in an object, there's no such thing as an immutable object. It's ironic that Java is sometimes viewed as a "teaching language", given that a more significant design goal was to simplify the runtime by using a single reference type; from a semantic perspective, it would have been better to distinguish types which have an identity from those which don't. The primary benefit of immutable types is... – supercat Jun 2 '14 at 15:13
  • ...that immutable instances which encapsulate the same state may be used interchangeably, but there's no way a type can guarantee that. If code uses new String("Hello"); as a key in an IdentityHashMap, the string would look like any other string encapsulating those characters to any code which didn't know of that map, but it wouldn't be substitutable because its underlying object would have an identity token that was different from every other object throughout the entire universe. – supercat Jun 2 '14 at 15:16
  • It does not matter whether the lock is technically contained or stored in external data structure, semantically it behaves as mutable property that allows distinguishing identity of the object. And I am almost sure I saw the low level description of Java Object where it clearly included the lock. One of the reasons for Java's extreme memory inefficiency. – Jan Hudec Jun 2 '14 at 17:39
  • @JanHudec: Semantically, no object can be more immutable than Object. Any definition of class-level immutability which would be satisfied by e.g. Integer would be equally satisfied by Object. Further, in general when an object is said to encapsulate mutable state, it's possible to meaningfully copy that state from one instance to another. I don't think there's any way of copying the lock state of one object to another. – supercat Jun 2 '14 at 18:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.