Referential and structural equality in Kotlin - kotlin

What is the difference between referential equality and structural equality
in Kotlin?
val a = File("/myfile.txt")
val b = File("/myfile.txt")
val sameRef = a === b
and:
val a = File("/myfile.txt")
val b = File("/myfile.txt")
val both= a == b

Referential equality === (also called identity) means that the pointers for two objects are the same. That is to say the objects are contained in the same memory location which leads us to the fact that pointers reference to the same object.
identity: determines whether two objects share the same memory address
Structural equality ==, in its turn, means that two objects have equivalent content. You should specify when two objects should be considered equal by overriding the equals() method.
equality: determines if two object contain the same state.
As well as in Java, in Kotlin there're no specific equals() and hashCode() generated by default (not considering data classes). Thus, until you've overriden these methods for your class, both == and === perform identity comparison.

Related

Why is the key type parameter of a Kotlin Map invariant?

The Map interface in Kotlin (using V1.6.21) has a signature of
interface Map<K, out V>
Why is K invariant instead of covariant (out K)?
The documentation of type parameter K says:
The map is invariant in its key type, as it can accept key as a parameter (of containsKey for example) and return it in keys set.
However, interface Set is covariant in the element type, so the the last part ("return it in keys set") is not applicable, at least not immediately.
Further, the type parameter K is used only at occurrences where the map state is not modified, for lookup purposes (methods containsKey, get, getOrDefault). At these places, isn't it safe to use #UnsafeVariance? After all, that same technique was employed to Map's value type parameter V, for example in containsValue, to allow making V covariant.
My guess would be that using a Map<KSubtype, V> as a Map<KSupertype, V> (where KSubtype : KSupertype) does not really make a lot of sense because the former, by construction, cannot contain entries with keys other than KSubtype.
So a proper implementation should return null from all calls to get(kSupertype) as well as return false from those to containsKey(kSupertype).
In the case of Set<out E> it's only the contains function that needs unsafe variance, and Map would also require unsafe variance on get. This might have been too much of a peculiarity to support, compared to the value of supporting the use case.

Referential vs Structural equality in Kotlin in case of strings

My current understanding of structural equality is that it compares the type and the content.
For Referential equality, it compares the address of the two objects. Also, in the case of a primitive type of var, it will print true if the content is the same.
According to the doc here --> numbers, characters and booleans can be represented as primitive values at runtime - but to the user, they look like ordinary classes.
So String should be treated as objects at runtime.
But I get true when comparing referentially two strings that have the same content.
fun main(){
val name1 = "123"
val name2 = "123"
println(name1 == name2) //true, structural equality which is same type and same content (same as equals)
// checks if left and right are same object --> referential equality
var name3 = "123"
println(name1 === name3) //true ????????
// should print false. String is basic type and not primitve
var Arr: IntArray = intArrayOf(1,2,3)
var Arr2: IntArray = intArrayOf(1,2,3)
println(Arr === Arr2) // prints false, arrays are basic type
}
Also, why doesn't equality in kotlin of both types differentiates between val and var? They are two different types of objects at their core.
Can someone point out where am I going wrong with this? I might be missing something pretty obvious here.
So as far as string referential equality check goes, I think it does the same thing as Java in that it adds everything to the string constant pool.
Here is the thread for referential string comparison in java (done by ==) -
What makes reference comparison (==) work for some strings in Java?
For Val vs Var the answer by #Tenfour04 below explains the logic.
val and var are not different types of objects. They are references, not objects at all. They have nothing to do with the behavior of the objects they are referring to. When you compare two objects, the types of variables and properties that are referencing them is not a factor whatsoever. When you call equals on one object, the runtime only uses the reference to find the object in memory. Only then is its function called.

Checking Sig Equality in Alloy

In the following Alloy model I want to check the equality of two instances of a sig with Bool field type:
module test
open util/boolean as bool
sig Info {
active: Bool
}
assert assertion {
all x1, x2: Info |
x1.active = True && x2.active = True implies x1 = x2
}
check assertion for 10
This model checks for equality of x_1 and x_2 if both have True as their active field. Alloy comes back with a counterexample, however, in the counterexample, both x_1 and x_2 are structurally equal but for some reason Alloy considers them not equal.
Edit:
One suggestion is to use subtyping as follows:
sig Info {}
sig ActiveInfo in Info {}
-- i is inactive if i in (Info - ActiveInfo)
However, this is not suitable for my model.
Quote from the Software Abstractions book:
"
Is equality structural equality or reference equality?
A relation has no identity distinct from its value, so this distinction,
based on programming notions, doesn’t make sense here. If two relations have the same set of tuples, they aren’t two relations: they’re just
one and the same relation. An atom is nothing but its identity; two atoms are equal when they are the same atom. If you have a set of atoms
that represent composite objects (using some relations to map the atoms to their contents), you can define any notion of structural equality
you want explicitly, by introducing a new relation. (And for those C++
programmers out there: no, you can’t redefine the equals symbol in Alloy.)"
I don't quite understand this paragraph. I appreciate explanation on how equality works in Alloy. Particularly on how to check equalities of atoms with different identities but same values?
I know that equality in Alloy is based on values.
This is not true. x1 and x2 have the same value for active, but are different atoms in the signature. It's similar to how in a lot of OOP languages, two objects can have the same structural values but have different identities.
Incidentally, I'd recommend using subtypes to represent booleans. You could do
sig Info {}
sig ActiveInfo in Info {}
-- i is inactive if i in (Info - ActiveInfo)
You can think of a field in a signature as "belonging" to an atom of that signature if you like, but it's better just to think of fields as relations. You wouldn't expect two persons to be the same if they have the same mother:
sig Person {mother: Parent}
But if you want your signature to have the property that no two distinct members have the same fields, you can just add that as a fact:
sig Coordinate {x, y: Value}
fact {all c, c': Coordinate | (c.x = c'.x and c.y = c'.y) implies c = c'}

Why does Kotlin have Mutable versions of collections?

I have a general question about Kotlin collections.
Why are there mutable versions of so many collections (like the MutableList) when we have the val vs var distinction?
Well....ok...actually, I understand that val doesn't have anything to do with the 'mutability' of the object, but rather the 're-initializability' of the object.
But then that raises the question....why isn't MutableList the default?
TL;DR
Individually, mutable and immutable collections are able to expose useful features that can't co-exist in a single interface:
Mutable collections can be read from and written to. But Kotlin strives to avoid all runtime failures, therefore, these mutable collections are invariant.
Immutable collections are covariant, but they're...well...immutable. Still, Kotlin does provide mechanisms for doing useful things with these immutable collections (like filtering values or creating new immutable collections from existing ones). You can go through the long list of convenience functions for Kotlin's (immutable) List interface for examples.
Immutable collections in Kotlin cannot have elements added or removed from them; they can only be read from. But this apparent restriction makes it possible to do some subtyping with the immutable collections. From the Kotlin docs:
The read-only collection types are covariant...the collection types have the same subtyping relationship as the element types.
This means that, if a Rectangle class is a child of a Shape class, you can place a List<Rectangle> object in a List<Shape> variable whenever required:
fun stackShapes(val shapesList: List<Shape>) {
...
}
val rectangleList = listOf<Rectangle>(...)
// This is valid!
stackShapes(rectangleList)
Mutable collections, on the other hand, can be read from and written to. Because of this, no sub-typing or super-typing is possible with them. From the Kotlin docs:
...mutable collections aren't covariant; otherwise, this would lead to runtime failures. If MutableList<Rectangle> was a subtype of MutableList<Shape>, you could insert other Shape inheritors (for example, Circle) into it, thus violating its Rectangle type argument.
val rectangleList = mutableListOf<Rectangle>(...);
val shapesList: MutableList<Shape> = rectangleList // MutableList<Rectangle>-type object in MutableList<Shape>-type variable
val circle = Circle(...)
val shape: Shape = circle // Circle-type object in Shape-type variable
// Runtime Error!
shapesList.add(shape) // You're actually trying to add a Circle to a MutableList<Rectangle>
// If rectanglesList couldn't be put into a variable with type MutableList<Shape> in the first place, you would never have run into this problem.
At this point, you might be thinking: "So what? Kotlin could just add type-checks to all of the write-methods of Mutable Collections...then you could allow them to be covariant, and you wouldn't need separate immutable collections!"
Which is true, except that it would go completely against a core Kotlin philosophy; to avoid nulls and runtime errors whenever possible. You see, the methods of such a Collection would have to return null - or raise an Exception - whenever a type-check fails. This would only become apparent at runtime, and since that can be avoided by simply making Mutable Collections invariant...that's exactly what Kotlin does.
From the Kotlin docs:
The read-only collection types are covariant. This means that, if a Rectangle class inherits from Shape, you can use a List<Rectangle> anywhere the List<Shape> is required. In other words, the collection types have the same subtyping relationship as the element types. Maps are covariant on the value type, but not on the key type.
In turn, mutable collections aren't covariant; otherwise, this would lead to runtime failures. If MutableList<Rectangle> was a subtype of MutableList<Shape>, you could insert other Shape inheritors (for example, Circle) into it, thus violating its Rectangle type argument.
To paraphrase, if it's immutable you know all the types are the same. If not, you might have different inheritors.

Equals two object open classes

fun main()
{
println(A() == B())
}
open class A
open class B
Why the сompiler doesn't show an error on equals? If the classes are not open, there is an error Operator '==' cannot be applied to 'A' and 'B'.
(Based on my understanding of https://discuss.kotlinlang.org/t/strange-behavior-with-equality-checking/7289/2)
For classes, calling x == y is equal to calling x.equals(y). By default, equals() is a reference equality: each object is only equal to itself.
Now, consider the cases:
If both classes are not open, then their equals() can't change. Therefore, the result of comparison is always false. Since it's likely an unintended behavior, the compiler shows an error.
If exactly one class is open, then its subclasses can override its equals() behavior.
One may think that now the condition x == y may potentially become true. However, equality must satisfy some properties, and one of them is symmetry: result of x == y must be the same as y == x. Since the second class can't override its equals() behavior, the condition is still always false.
If both classes are open, then both their subclasses may override equals() so that it becomes true without violating any properties of equals().