Hi, I need an immutable sorted map. Which type sho...
# announcements
m
Hi, I need an immutable sorted map. Which type should I use? •
mapOf()
is not sorted •
sortedMapOf()
is mutable •
hashMapOf()
is mutable and not sorted •
linkedMapOf()
is mutable Any other suggestions?
n
None of these are really immutable
rather I should say, I don't think any of them are guaranteed to be immutable, in general
you can achieve your goal relatively easily by writing a small class that wraps a sorted map
r
why can’t you just do Collections.unmodifiableMap(mysortedmap)
💯 2
n
and delegates the
Map
interface
what Ryan said 🙂
🙂 1
I can't see where that's documented though
m
will it still be sorted?
r
yes
it just makes a map that accesses the map but put doesn’t work
n
It's probably doing exactly the same thing that I suggested
m
and if I use
mapOf()
, it won't be sorted, right?
n
Copy code
class foo<K, V>(val data: SortedMap<K, V>): Map<K, V> by data
more or less
m
what does
by data
mean?
r
It means implement the interface by delegating all function calls to this property which implements the same interface.
m
Cool, thanks. I guess this will work
j
No, it wouldn't be sufficient. The values and keys methods for example would still return mutable collections.
c
Isn't
Collections.unmodifiableMap
read-only, not immutable?
n
@jbnizet They would return read-only interfaces, which may or may not be downcastable to something mutable
@CLOVIS not sure how it works, but assuming it's similar to above, you can make it "mostly" immutable if you have a defensive copy. After that, the only way to mutate it, if at all, is something like what's discussed in the message
Note that foo itself cannot be directly downcast to anything mutable
actually, you can apply the same trick to wrap anything coming out of foo that might be downcastable, like values or keys
Annoying but not terrible and don't see anything obviously better
r
Collections.unmodifiableMap
returns a view of a Map which will throw an exception if you call any mutating method on it, and which does not permit access to the wrapped map. So the returned Map is immutable if the wrapped Map never escapes (unless you do something evil with reflection). Hence the result of
Collections.unmodifiableMap(sortedMapOf("x" to 1))
is immutable; this includes the collections returned by
keys
and
entries
and
values
. Ironically the type of
Collections.unmodifiableMap(sortedMapOf("x" to 1))
is
MutableMap
! This is because any method returning
java.util.Map
is considered as returning a
kotlin.collections.MutableMap
.
n
Weird. That allows for some dishonest downcasting
d
We need to start using the term "read-only" more.
n
Yeah. That's what the official documentation uses. 95 percent of usages of "immutable" I see here or in the Kotlin subreddit are incorrect
c
Java's
Collections.immutable*
returns a wrapper around the original collection, that throws on all attempts to modify it
r
Weird. That allows for some dishonest downcasting
It’s not really -
java.util.Map
dates to before the JVM world was converted to Immutability, so its interface implies mutability.
java.util.Collections.unmodifiableMap
consequently had to return a
java.util.Map
with all the mutating methods, but make them fail at runtime (though I suppose they could have retrofitted a
ReadOnlyMap
supertype…). It’s perfectly reasonable for kotlin to acknowledge
java.util.Map
’s apparent mutability by using
MutableMap
as a synonym. Which makes it quite correct that if you call a java method that returns
java.util.Map
(like
java.util.Collections.unmodifiableMap
) from kotlin you’ll be returned a
kotlin.collections.MutableMap
. The weird bit is that there isn’t a kotlin equivalent to
unmodifiableMap
in the stdlib, though perhaps they felt that in practice no-one would downcast the read only interface?
n
That's great explanation and background, and by all appearances that was the right choice
That doesn't make what I said wrong either though. In general implementing some interface but throwing when any of the methods are called is "dishonest", you're lying about the type implementing something that it really doesn't, in the process converting compile time errors into runtime errors
In this case saying you're a MutableMap when you actually throw when any of it's methods are called (which aren't also Map methods), is dishonest :-) you're lying about the type implementing mutable map, so the downcast will be succesy, but not subsequent method calls
c
There is no Kotlin equivalent in the stdlib, but it's a one-liner:
data class ReadOnlyMap<K, V>(private val map: Map<K, V>) : Map<K, V> by map
Still though, it's read-only, not immutable.
I kind of miss C++'s
const
functions but I don't think the JVM's capable of having anything like that (maybe with value types from Project Valhalla?)
n
const is nice but it's a lot less valuable in GC languages
Read only map doesn't have to be a dataclass. You can also mitigate the issues with it by making the primary constructor private and making a secondary constructor which does a defensive copy
c
I've never needed it in Java, but in Kotlin I think there's real value to having truly immutable data classes with pure functions on them, because it would play very nicely with
suspend
r
I like Clovis solution - very nice.
a
@Mark Buikema What about
sortedMapOf().toMap()
?
r
Couple of things to know about
Map.toMap()
- while it returns
Map
, it’s actually a
MutableMap
so a malevolent/incompetent actor could downcast and mutate it. And it makes a copy of the map it was called on, so you’ll be constructing two map instances. CLOVIS’
ReadOnlyMap
class doesn’t have either issue.
c
(and you could easily add a
Map.toReadOnly()
which is a one-liner and just as easy to use ^^)
a
@Rob Elliot CLOVIS map is mutable as well if you keep a reference to the map that you pass as argument. In order to be sure that nobody edits the map a copy has to me made.
r
Yes, obviously - I presume that’s why CLOVIS called it
ReadOnlyMap
instead of
ImmutableMap
. But if you don’t allow the original reference to escape then the value it returns isn’t mutable, whereas the value returned by
Map.toMap
can always be downcast to
MutableMap
and mutated.
I think this covers most bases; though untested:
Copy code
Removed, it was nonsense...
I’m a prat, forgot that
SortedMap
is a Java interface so mutable, so all of the returned objects there were mutable 😳
An underlying problem is that kotlin doesn’t have a
SortedMap
interface, it’s just
java.util.SortedMap
, so it’s hard to get the
SortedMap
specific methods (like
subMap
,
firstKey
) without also getting all the mutating methods. I haven’t fully understood how kotlin manages to make
kotlin.collections.Map
interchangeable with
java.util.Map
without adding the mutating methods to its signature.