In Kotlin, there's a bit of an issue where even if...
# announcements
n
In Kotlin, there's a bit of an issue where even if you have something like this:
Copy code
data class Foo(val bar: List<T>)
Despite the use of both val and List (as opposed to MutableList), it's still pretty easy to mutate data out from underneath Foo. I was curious how many people had felt the need to have some kind of true immutable list type, to prevent this scenario? One pleasant surprise in Kotlin was that I was able to implement something reasonable in just a handful of lines. Even just:
Copy code
class ImmutableList<T> private constructor(val data: List<T>) : List<T> by data {
    constructor(x: Sequence<T>) : this(x.toList())
    constructor(x: Iterable<T>) : this(x.toList())
}

operator fun<T> ImmutableList<T>.plus(x: Sequence<T>) = (data.asSequence() + x).toImmutableList()
operator fun<T> ImmutableList<T>.plus(x: Iterable<T>) = (data + x).toImmutableList()
operator fun<T> ImmutableList<T>.plus(x: T) = plus(sequenceOf(x))

fun <T> immutableListOf(vararg elements: T) = elements.asIterable().toImmutableList()
fun <T> Sequence<T>.toImmutableList() = ImmutableList<T>(this)
fun <T> Iterable<T>.toImmutableList() = ImmutableList<T>(this)
Is already pretty usable (thanks mostly to delegation). And now we can go back to our previous example:
Copy code
data class Foo(val bar: ImmutableList<T>)
This is actually conditionally immutable depending on the T (say it's an Integer or String). I'm curious if many people are using things similar to this? Or are people just living with the possibility of unexpected mutations in Foo, finding it not too big of a problem?
z
Have you seen this? https://github.com/Kotlin/kotlinx.collections.immutable I believe the Compose project is using this.
💯 1
n
Yeah, I was looking through that. I think it will be a good thing to add anything like that. having both immutable and persistent, in the slightly different senses, is nice.
I was a bit curious/concerned about the build/mutate API, seems hard to understand how it can both be efficient, and safe
I guess the best way to figure it out is to download the project and go through the source
Ah I see, it's pretty fancy 🙂 that makes sense then
b
I'm curious how do you mutate List or val?
n
Copy code
val x = mutableListOf(1,2,3)
val f = Foo(x)
x.add(4)
b
😀
m
Otherwise you could just employ defensive copying in the constructor
n
@Matteo Mirk you could but it feels horrible to have to do that
You're also pre-emptively making a copy which may or may not be necessary
If you instead use the
ImmutableList
class or something like it, then you don't need the defensive copying. Also, I provide a version of
map
that returns an ImmutableList instead of List. If you think about it
map
is returning a List that nobody has a mutable reference to, so it's already "safe" shy of downcasting. Just need to reflect that in the type system.
m
Honestly, I don’t care. 🙂 If all these data passings happen inside my codebase I can make sure of avoiding mutation bugs and always use unmodifiable
List
instances. If data comes from an external client I can just defensive copy unless we’re dealing with thousands or million elements, or require an ImmutableList if the need arises.
n
If you're the only person working on your codebase, sure. In that case, a lot of things are simple though 🙂
👍 1