views:

171

answers:

5

Is there a way to convert (wrap) a mutable Map to immutable in O(1) time (that is, not by copying the values, but similar to what is done in JavaConversions)

+5  A: 

There is a read only projection for mutable maps.

scala> collection.mutable.Map(1->2).readOnly
res0: scala.collection.Map[Int,Int] = ro-Map(1 -> 2)

As oxbow_lakes pointed out the underlying Map is still mutable and may change after the read-only projection is published to clients. The illusion of immutability has to addressed in code managing the map.

Thomas Jung
It looks like this is a 2.7 method. It is commented out in 2.8.0-Beta1 and the nightly docs don't show it.
IttayD
+2  A: 

What you are asking for is inherently unsafe. You can either pass the mutable.Map around as a collection.Map which is immutable but "clients" using this form cannot be sure that their view will not change from under them.

oxbow_lakes
Your point is valid. Read-only map will only work if the underlying Map is not changed after clients have got a reference to it. The usage is not safe is this respect. This is a trade-off and won't be to a big problem for real use cases as long as you publish only the read-only projection just like you wold use java.util.Collections.unmodifiableMap.
Thomas Jung
I find that I use the Java `unmodifiableXyz` when I wish to pass some initialized collection outside of the class which *owns* it. That is to say that I do this in cases where I can be sure that the data will not change from under a reader, I only wish to ensure that a reader cannot modify it. My solution above is therefore unsuitable I think
oxbow_lakes
+4  A: 

As Thomas points out, the read only view is O(1). But read-only doesn't equate to immutability.

The difference is well descrived in the "Fighting Bit Rot" paper:

All collection classes are kept in a package scala.collection. This package has three subpackages: mutable, immutable, and generic. Most collections exist in three forms, depending on their mutability.

A collection in package scala.collection.immutable is guaranteed to be immutable for everyone. That means one can rely on the fact that accessing the same collection value over time will always yield a collection with the same elements. A collection in package scala.collection.mutable is known to have some operations that change the collection in place.

A collection in package scala.collection can be either mutable or immutable. For instance, collection.Seq[T] is a superclass of both collection.immutable.Seq[T] and collection.mutable.Seq[T]. Generally, the root collections in package scala. collection define the same interface as the immutable collections, and the mutable collections in package scala.collection.mutable typically add some destructive modification operations to this immutable interface. The difference between root collections and immutable collections is that a user of an immutable collection has a guarantee that nobody can mutate the collection, whereas users of root collections have to assume modifications by others, even though they cannot do any modifications themselves.

Perhaps it's just a simple as up-casting.

scala> val mm = collection.mutable.Map(1 -> 2)
mm: scala.collection.mutable.Map[Int,Int] = Map(1 -> 2)

scala> val readOnly = mm : collection.Map[Int, Int]
readOnly: scala.collection.Map[Int,Int] = Map(1 -> 2)
retronym
The difference between casting and map.readOnly is that the user could cast collection.Map back to an mutable map or use reflection to change the map.
Thomas Jung
Good point. It looks like the 2.8 solution to this might be using `MapProxy`, but I can't easily wrap a `collection.MapProxy` around a `colletion.mutable.Map` yet. e.g `val mm = collection.mutable.Map(1 -> 2); new collection.MapProxy[Int, Int] { val self = mm }`
retronym
+3  A: 

One could in principle add a "freeze" method to a mutable data structure that prevented further mutation. This is the only even slightly-safe way to do the wrapping. (Only slightly because after that you'd have to throw exceptions when you tried to mutate it.) Scala's mutable collections don't have this capability, however. One could add it to e.g. mutable.HashMap by overriding all mutating methods (update, +=, ++=, etc.), but this would be a fair bit of work.

Rex Kerr
+1  A: 

Philipp Haller's work on Capabilities for Uniqueness and Borrowing is related to this. There's a lot of other work in the domain of enforcing "ownership" through the type system, but Philipp actually provides a usable plugin to the Scala compiler.

Adriaan Moors