Home > Mobile >  Can you make a list of value classes, with the list being bound to the list of values?
Can you make a list of value classes, with the list being bound to the list of values?

Time:10-12

Since value classes (aka inline classes) are not classes at runtime but the value class, is it possible to make a list of them, and the list is bound to the list of values?

If i couldn't explain that very clearly, here is a code example:

class Foo(var i)

@JvmInline
value class FooWrapper(val foo: Foo)

fun main() {
    val fooList = mutableListOf(Foo(1), Foo(2))
    val wrappedFooList = fooList.???<FooWrapper>()

    // fooList and wrappedFooList are the same list at runtime, so when you insert a value to fooList, it gets "added" to wrappedFooList as `FooWrapper(added)`
    
    // This is what im currently using, but this is a seperate list, so when a value gets inserted into fooList, it doesn't get inserted here.
    val wrappedFooListButNotTheSameList = fooList.map { FooWrapper(it) }

    fooList.add(Foo(3)) // FooWrapper(Foo(3)) now exists in wrappedFooList
}

CodePudding user response:

Since value classes (aka inline classes) are not classes at runtime but the value class

This is only true sometimes. Inlining does not always happen, and the class file for the inline class does exist.

As soon as you start using generics, inlining goes out of the window. That is, your list of FooWrapper would not be inlined at all.

Documentation:

However, sometimes it is necessary to keep wrappers around. As a rule of thumb, inline classes are boxed whenever they are used as another type.

See also the code sample that follows that. This is likely because when they are used as another type, code that doesn't know about the inline class is likely going to be interacting with the wrapper, and unexpected behaviours would occur if they are not boxed.

With all that in mind, if you still want two lists of unrelated types, that are "linked" together, you can first encode the conversion between the types with an interface:

interface ConvertibleTo<T> {
    val converted: T
}

data class Foo(var i: Int): ConvertibleTo<FooWrapper> {
    override val converted get() = FooWrapper(this)
}

@JvmInline
value class FooWrapper(val foo: Foo): ConvertibleTo<Foo> {
    override val converted get() = foo
}

Then make a ConvertList<T, U> and a ConvertListIterator<T, U> by delegating everything (yes this is a lot of boilerplate). The built-in by can't help here because you are also adding an extra .converted on every U value. Instead of the interfaces, you can also add T.() -> U and U.() -> T in the constructor parameters.

class ConvertList<T: ConvertibleTo<U>, U: ConvertibleTo<T>>(private val list: MutableList<T>): MutableList<U> {
    override val size: Int
        get() = list.size

    override fun contains(element: U) = list.contains(element.converted)

    override fun containsAll(elements: Collection<U>) =
        list.containsAll(elements.map(ConvertibleTo<T>::converted))

    override fun get(index: Int) =
        list[index].converted

    override fun indexOf(element: U) =
        list.indexOf(element.converted)

    override fun isEmpty() = list.isEmpty()

    override fun iterator() = ConvertListIterator(list.listIterator())

    override fun lastIndexOf(element: U) = list.lastIndexOf(element.converted)

    override fun add(element: U) = list.add(element.converted)

    override fun add(index: Int, element: U) = list.add(index, element.converted)

    override fun addAll(index: Int, elements: Collection<U>) =
        list.addAll(index, elements.map(ConvertibleTo<T>::converted))

    override fun addAll(elements: Collection<U>) =
        list.addAll(elements.map(ConvertibleTo<T>::converted))

    override fun clear() = list.clear()

    override fun listIterator() = ConvertListIterator(list.listIterator())

    override fun listIterator(index: Int) = ConvertListIterator(list.listIterator(index))

    override fun remove(element: U) = list.remove(element.converted)

    override fun removeAll(elements: Collection<U>) =
        list.removeAll(elements.map(ConvertibleTo<T>::converted))

    override fun removeAt(index: Int) = list.removeAt(index).converted

    override fun retainAll(elements: Collection<U>) =
        list.retainAll(elements.map(ConvertibleTo<T>::converted))

    override fun set(index: Int, element: U) = list.set(index, element.converted).converted

    override fun subList(fromIndex: Int, toIndex: Int) = ConvertList(list.subList(fromIndex, toIndex))
}

class ConvertListIterator<T: ConvertibleTo<U>, U: ConvertibleTo<T>>(private val iter: MutableListIterator<T>): MutableListIterator<U> {
    override fun hasPrevious() = iter.hasPrevious()

    override fun nextIndex() = iter.nextIndex()

    override fun previous() = iter.previous().converted

    override fun previousIndex() = iter.previousIndex()

    override fun add(element: U) = iter.add(element.converted)

    override fun hasNext() = iter.hasNext()

    override fun next() = iter.next().converted

    override fun remove() = iter.remove()

    override fun set(element: U) = iter.set(element.converted)

}

Usage:

val list = mutableListOf(Foo(1), Foo(2))
val list2 = ConvertList(list)
  • Related