In Algol68 there is a continuous progression of types called int, ref int, ref ref int, which is roughly like C’s const int, int, int *. Notice that C’s progression is irregular. I am sure that the C theorists would disagree but I disagree with their ontology. I think that it causes some of the problems that people argue about regarding EQ.
In the Algol68 world an assignment exp = expp requires that the type of exp begin with “ref”. The concept of lvalue is unnecessary.
I discovered in a recent Java program that I wrote that over 95% of the parameters and “variables” should be declared “final”. Such makes the program easier to comprehend by both the human and the compiler. The conventional languages are off by one damn it! If Java made everything final by default and had a way of declaring an identifier to be mutable (perhaps the keyword “variable”) then it would be much closer to Algol68 in this regard.
What has this got to do with EQ?
In Algol68 I can have a row int, which is to say an array of integers. An array of integers, like [3, 4, 5], is no more mutable than the integer 3. An identifier x may designate a row int, in which case assignments may not be made to the row or its elements. If you want a mutable array of integers you write ref row int. If you want an array of variable integers you may write row ref int. These are different and both useful, but that is a digression.
In this context it seems perfectly clear what equality means. If x and y are declared int then x=y just in case they refer to the same integer. If they are declared ref int then x=y just in case they refer to the same variable RAM location, in which their single value varies. In this case an assignment to one, x:=3, affects the other as well. Otherwise if you want to see if the distinct RAM cells refer to the same integer you query .x=.y . (Actually this latter syntax if from Bliss, Algol’s syntax is awkward here for reasons that need not concern E.)
I know of no engineering that has been done to adapt Algol68’s value ontology to the style of data abstraction typical of OO languages. On the other hand Algol68 had essentially the same attitude as Scheme regarding procedure values—they were first class values. The primary difference was that official Algol68 didn’t require persistence although several implementations did provide persistent procedure values. A closure, or procedure value is enough like an object to make search for solutions analogous to Algol68. Alas Algol68 finks out the same way Scheme does regarding EQ for procedure values: if p=q then p and q are guaranteed to be interchangeable, but not conversely. p=p is guaranteed.
If x and y are declared row int then x=y requires that the lengths be the same and each element agree with the other. This remains true whether or not the computing platform has been smart enough to store these array values in the same RAM. If they are declared ref row int then x=y asks if they vary together (share RAM) or separately. .x=.y dereferences the array and compares the immutable array currently occupying RAM. If they are declared row ref int then the question is whether each reference of one matches (shares RAM) with the corresponding element of the other.