I was about to suggest that:
When overriding a method that returns Int, the override should be allowed to specify that it returns a value class that has a single Int field, as if it derived from Int, and also that a for-loops with an IntIterator overload that returns a value type with a single Int field would use that value type as the element type,
But realized that doesn’t work either. Because the goal was a pseudo-generic collection class, which would therefore have a pseudo-generic iterator: IntValueIterator<T> class, with an override fun nextInt(): T method… but without pseudo-generics, the compiler has no way to know that T is a single-Int value class, and instead treats it as Object under the covers, which would fail to compile.
So the context trick still won’t let us implement MyCollection<IntValueClass> #iterator() in a way that allows for loops to execute without heap allocations.
The smallest language extension I can think of is to add a interface IntValueIterator<T>: IntIterator {}, and then Kotlin could have a hack to allow for (element:IntValueClass in IntValueIterable<IntValueClass>), even though IntValueIterator<T>.next() returns Integer and not IntValueClass. That’s ugly though.
Much cleaner to just add value pseudo-generics.
I have just discovered that Java does have a solution for this, ironically. java.util.PrimitiveIterator<T, TConsumer> is specialized by the JVM to avoid allocations! Obviously, I need a thin wrapper:
data class VIntArrayIterator<T>(val delegate:IntIterator, val a: ValueIntAdapter<T>): PrimitiveIterator<T, VIntConsumer<T>> {
override inline fun hasNext(): Boolean = delegate.hasNext()
override inline fun remove(): Unit = delegate.remove()
override inline fun next(): T? = a.fromInt(delegate.next())
override inline fun forEachRemaining(v: VIntConsumer<T>?): Unit = delegate.forEachRemaining(IntConsumer{ v?.invoke(a.fromInt(it)) })
}
But it turns out this doesn’t compile because kotlin.IntArray.iterator() returns a kotlin.collections.IntIterator which is also specialized to not use allocations, but (A) does not derive from any PrimitiveIterator, and also (B) does not have a fun remove(): Unit method. So apparently I have to make remove unconditionally thow. Close enough, I guess.
It took me a full day, but I finally understand your confusion.
Int is treated as a primitive when it is a parameter, return type, or member, and Int? Is the boxed wrapper in those contexts. However, the root of the problem we’re discussing is that Int is treated as a boxed wrapper when used as a parameter to a generic class or method, because under the covers, *generic* parameters, return types, and members are actually Object in the JVM,
Therefore, use of primitives, and value types, in generic container interfaces like Array or List force every parameter and return type to be allocated on the heap.
AndroidX helpfully has a collections package with specialized IntArray , IntList, IntLongMap, etc, that do NOT inherit from java List or Map interfaces. But to use those with Kotlin’s value classes, every method call has to manually “serialize” and “deserialize” the value classes, which is inconvenient.
So my goal was a (hopefully) tiny tweak to Kotlin to make wrapper classes for these possible. Namely, if I could make a pseudo-generic class that did the serialization automatically, that would be amazing.
1 Like