Hi , is there any reason kotlin forbids identity e...
# stdlib
s
Hi , is there any reason kotlin forbids identity equality for value class !?
Copy code
import Three.Companion.toThree

@JvmInline
value class Three private constructor(val value: Int) {

  init {
    require(value >= 1)
    require(value <= 3)
  }


  companion object {
    private val array by lazy {
      arrayOf(Three(1), Three(2), Three(3))
    }

    fun Int.toThree(): Three {
      return if (this in 1..3) {
        array[this - 1]
      } else {
        val index = (this % 3).let { r -> if (r == 0) 3 else r }.let { v -> if (v < 1) v + 3 else v }
        array[index - 1]
      }
    }
  }
}


fun main() {
    println(1.toThree() == 4.toThree())
    println(1.toThree() === 4.toThree()) // Identity equality for arguments of types Three and Three is forbidden
}
Online playground here : https://pl.kotl.in/uBVYwgQgO?theme=darcula
e
https://github.com/Kotlin/KEEP/blob/master/notes/value-classes.md "Values classes are immutable classes that disavow the concept of identity for their instances."
s
wow ! TIL , thanks a lot ! But it's hard to do custom object pools ... 🤔
i
Could you describe your use case with value classes and object pools a bit more? Preferably in #language-proposals as this is not a question about stdlib.
i
Can you, please, add you use-case as a comment to https://youtrack.jetbrains.com/issue/KT-28301?
s
Hi @ilya.gorbunov , @Ilmir Usmanov [JB]: Just like my
value class Three
, I am building a carousel-like Int , naming
Three
, which restrict any Int value to
1..3
. That is
Three.of(1) == Three.of(4)
,
Three.of(0) == Three.of(3)
. I use a prebuild array to simulate caching , hoping they get the same reference
Copy code
private val array by lazy {
  arrayOf(Three(1), Three(2), Three(3))
}
But it seems it doesn't work. When comparing reference
===
, it complains
Identity equality for arguments of types Three and Three is forbidden
, which is counter-intuitive .
i
Can you explain your intuition, because I find it odd, that it is counter-intuitive?
s
I am returning
Three
from array :
Copy code
fun of(value: Int): Three {
      return value.toPeriod()
    }

   fun Int.toThree(): Three {
      return if (this in 1..3) {
        array[this - 1]
      } else {
        val index = (this % 3).let { r -> if (r == 0) 3 else r }.let { v -> if (v < 1) v + 3 else v }
        array[index - 1]
      }
    }
This should be the same reference . but in fact they are not same object reference.
Copy code
@Test
  fun testRef() {
    assertSame(1.toThree() , 4.toThree())
  }
The result :
Copy code
org.opentest4j.AssertionFailedError: expected: Three@46fa7c39<Three(value=1)> but was: Three@1fb700ee<Three(value=1)>
Expected :Three(value=1)
Actual   :Three(value=1)
i
Inline classes behave similar to primitives - they are boxed when upcast to Any (which happens, when they are passed as arguments to
assertSame
) function
Copy code
fun Int.add255(): Int {
    return this + 255
}

fun main() {
    assertSame(1.add255(), 1.add255())
}
also fails. So, this behavior is intuitive, if you consider inline classes as primitives.
s
Yes , that's because
Int
has a build cache ( -128 to 127 ) But I am returning object from array (just like the Int cache) . And I make sure they are not out of bound , there is no reason they are not referential equal. That's what I mean counter-intuitive . Just like this :
Copy code
fun Int.add1(): Int {
    return this + 1
  }

  @Test
  fun testInt() {
    assertSame(1.add1(), 1.add1())      // ok
    assertSame(127.add1(), 127.add1())  // failed , but reasonable , understandable
  }
e
Integer only gets cached instances through some boxing methods and not others, it's not something to rely on, and it's not something Kotlin can reuse - the box in this case is a different class
i
that's because 
Int
 has a build cache
No, this is because primitives are boxed. You are not returning an object from an array - you are returning inline class. If you need a reference - do not use referenceless (by design) inline classes. Similar to primitives, inline classes are boxed, thus they have unstable reference.
e
why do you even want to cache instances? like primitives, Kotlin will be using the unboxed/underlying type everywhere possible.
fun Int.toThree() = Three(this)
will, in bytecode, only use primitives
s
they have unstable reference.
. my gotcha today . to @ephemient
fun Int.toThree() = Three(this)
is not enough . I need to do bound checking, making sure not only 1.toThree() equals 4.toThree() , but also referential equal . But TIL it seems impossible.
156 Views