https://kotlinlang.org logo
k

Kirill Grouchnikov

11/23/2020, 10:39 PM
listOf
creates a whole new list. Java's
unmodifiableList
is a very thin wrapper that throws exceptions on trying to modify the contents of the wrapped list
e

ephemient

11/23/2020, 10:39 PM
just use
Collections.unmodifiableList()
if you're on JVM
k

Kirill Grouchnikov

11/23/2020, 10:40 PM
That's not helping me here. I'm looking explicitly for the Kotlin way
n

Nir

11/23/2020, 10:40 PM
unmodifiablelist is honestly pretty hacky. if you want a nice, kotlin solution I'd suggest looking at kotlinx.immutable.
but I'm not sure it meets your criteria of being a simple one liner
e

ephemient

11/23/2020, 10:41 PM
(that's immutable, not unmodifiable view)
I mean, IMO kotlin way would be don't reinvent what you don't have to
n

Nir

11/23/2020, 10:41 PM
ah alright didn't realize that unmodifiable list doesn't protect its contents
a

Arkadii Ivanov

11/23/2020, 10:42 PM
Don't cast List to MutableList. It may not succeed.
n

Nir

11/23/2020, 10:42 PM
Then I think this is the same as our discussion from the other day, right, ephemient? If you write a new class and use delegation, then the cast fails on the latest kotlin bytecode right?
k

Kirill Grouchnikov

11/23/2020, 10:43 PM
I can't prevent the callers from casting back to
MutableList
. That's the whole point here. I want a one liner that creates a thin, unmodifiable view on the original list. If there's no such thing, just say that.
e

ephemient

11/23/2020, 10:43 PM
JVM/IR, but that isn't fully ready yet
n

Nir

11/23/2020, 10:44 PM
@Kirill Grouchnikov it may not work 100% now, the most future proof way I think would be to write a simple one line class via delegation:
Copy code
class UnmodifiableList<T>(val list: List<T>) : List<T> by list
a

Arkadii Ivanov

11/23/2020, 10:44 PM
There is no such thing in stdlib. But listOf can't be cast to MutableList under new IR.
n

Nir

11/23/2020, 10:44 PM
This actually prevents callers from casting
a

Arkadii Ivanov

11/23/2020, 10:44 PM
Also Kotlin playground demonstrates this, it won't cast
n

Nir

11/23/2020, 10:44 PM
listOf can't be cast either in new IR? That's surprising
good to know
k

Kirill Grouchnikov

11/23/2020, 10:45 PM
listOf
is too heavyweight as it creates a whole new list and takes up a separate chunk of heap
e

ephemient

11/23/2020, 10:46 PM
@Nir: even on the new IR, List/MutableList has special guards, but the common idiom doesn't wrap the iterators so they can still be cast if you assume malicious users
n

Nir

11/23/2020, 10:46 PM
I guess it depends how far you want to go in fighting Machiavelli
e

ephemient

11/23/2020, 10:47 PM
a

Arkadii Ivanov

11/23/2020, 10:47 PM
@Nir actually I'm not sure now, need to check)
k

Kirill Grouchnikov

11/23/2020, 10:47 PM
I always assume malicious users when I'm writing library code
Or not even malicious, but rather non-compliant for whatever reason
n

Nir

11/23/2020, 10:47 PM
@Kirill Grouchnikov if you use UnmodifiableList above, then probably in the next few months as you upgrade Kotlin, move to new IR, it will prevent the cast from being successful
I dunno if you assume that then can't people just break everything using reflection?
k

Kirill Grouchnikov

11/23/2020, 10:49 PM
That would be highly intentional
e

ephemient

11/23/2020, 10:49 PM
if you have Java users, then I can understand being defensive about List/MutableList, because the distinction is not visible to them
k

Kirill Grouchnikov

11/23/2020, 10:50 PM
This is evolving into a theoretical discussion that I'm not that interested in. From what I'm reading here, the answer to my question is that there is no such one-liner thin wrapper right now.
e

ephemient

11/23/2020, 10:50 PM
however, if you are all Kotlin, I believe you should not bother. an List→MutableList cast, like reflection, is explicitly asking for it.
4
v

Vampire

11/23/2020, 11:00 PM
That's not really right. Most beginners learn how to cast, but not how to do reflection. And if they can cast, they will, even if there would be proper and safer methods for doing the same. That's not an assumption but a sad observation.
👍 1
n

Nir

11/23/2020, 11:07 PM
For me it's not a matter of knowledge, but intent. In C++ people often use the "protect against murphy, not machiavelli" which I think is a good adage. Protect vs things that people could do by accident typically because it looks a lot like common usage (like mutating a list), don't try to protect against people doing things deliberately that could break them by their nature
1
Is it even practical/possible to write a whole Kotlin library, "protecting" your users against downcasting? I think it's impossible to do broadly even in theory.
I guess you could do it if you never ever return any interfaces or open classes to your users, ever, if that is actually practical for your library.
z

Zach Klippenstein (he/him) [MOD]

11/23/2020, 11:09 PM
object : List<Foo> by yourListOfFoos {}
is one line and hides the underlying type like you want.
v

Vampire

11/23/2020, 11:09 PM
Copy code
fun <T> List<T>.asUnmodifiableList() = object : List<T> by this { }

val unmodifiableList = mutableList.asUnmodifiableList()
e

ephemient

11/23/2020, 11:10 PM
note my earlier pl.kotl.in. yes, but that is easy to get around by casting the iterator. how far are you willing to defend against?
also breaks equality and hashcode and tostring...
v

Vampire

11/23/2020, 11:12 PM
Well, one has to die one death 😄
n

Nir

11/23/2020, 11:14 PM
@Zach Klippenstein (he/him) [MOD] yep that was my suggestion as well, and in an earlier thread with ephemient, but this doesn't actually work unless you're on the new IR
(ephemient is the one who pointed this out to me)
e

ephemient

11/23/2020, 11:19 PM
well, it works in that it prevents direct modification. but it doesn't prevent the upcast in Kotlin.
z

Zach Klippenstein (he/him) [MOD]

11/23/2020, 11:21 PM
It prevents the cast for me, without IR. It’s an entirely different type, it can’t be cast to
MutableList
.
n

Nir

11/23/2020, 11:23 PM
that was my reasoning as well, but ephemient said he tried it and was able to do the cast locally (I didn't try, only on the playground which is using IR) 🤷
z

Zach Klippenstein (he/him) [MOD]

11/23/2020, 11:27 PM
i just did it locally, it definitely doesn’t work. I don’t see how it could –
object :
creates a new type, and completely unrelated types can’t be cast to each other on the JVM. Unless we’re trying subtly different code?
n

Nir

11/23/2020, 11:28 PM
hmm I was doing
class foo(val x: List<Foo>) : List<Foo> by x
i.e. class instead of object
v

Vampire

11/23/2020, 11:28 PM
Copy code
fun <T> List<T>.asUnmodifiableList() = object : List<T> by this {}
 
 val mutableList = mutableListOf(1, 2, 3)
 println(mutableList)
 mutableList.add(4)
 println(mutableList)
 val unmodifiableList = mutableList.asUnmodifiableList()
 println(unmodifiableList.toList())
 mutableList.add(5)
 println(unmodifiableList.toList())
 (unmodifiableList as MutableList).add(6)
 println(unmodifiableList.toList())
Copy code
[1, 2, 3][1, 2, 3, 4][1, 2, 3, 4][1, 2, 3, 4, 5]java.lang.ClassCastException: Line_0$asUnmodifiableList$1 cannot be cast to kotlin.collections.MutableList
e

ephemient

11/23/2020, 11:31 PM
yeah, fails in Kotlin REPL
but if you actually try to compile it, the cast succeeds
n

Nir

11/23/2020, 11:31 PM
@ephemient I'm trying it locally and it fails
e

ephemient

11/23/2020, 11:31 PM
Copy code
fun main() {
    object : List<Any> by emptyList() {} as MutableList<Any>
}
Kotlin 1.4.10 here, I'll give 1.4.20 a try in a sec
n

Nir

11/23/2020, 11:32 PM
I'm on 1.4.0 it looks like
according to my build.gradle anyway
v

Vampire

11/23/2020, 11:33 PM
Copy code
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3, 4]
[1, 2, 3, 4, 5]
Exception in thread "main" java.lang.ClassCastException: de.empic.teamcity.config.FooKt$asUnmodifiableList$1 cannot be cast to kotlin.collections.MutableList
	at de.empic.teamcity.config.FooKt.main(Foo.kt:14)
	at de.empic.teamcity.config.FooKt.main(Foo.kt)
From a compiled .kt file
1.3.70 actually
e

ephemient

11/23/2020, 11:34 PM
hmm, I do not know what is up with this 🤷
in any case, I don't think it's worth it. do the whole unmodifiable wrapper right, or don't; half-measures mean you fail in harder-to-anticipate ways
👍 1
n

Nir

11/23/2020, 11:38 PM
This specifically I don't think is worth it because it gives you almost no real protection
But, a real
ImmutableList
that defensively copies the constructor, or otherwise provides functions where it knows it's "safe" is well worth it
Having a class with a
List
member that gets modified from halfway across your program, is very very well within the realm of Murphy
I had a bad bug from this in python literally yesterday
v

Vampire

11/23/2020, 11:53 PM
Immutable is totally different to unmodifiable vie though.
n

Nir

11/23/2020, 11:54 PM
Yes, agree
4 Views