Could you Imagine a Java where you have a `Map` and a `MutableMap` and that's what you put at your API? I'd make it SO much clearer how safe any individual API is to call.
Could you Imagine a Java where you have a `Map` and a `MutableMap` and that's what you put at your API? I'd make it SO much clearer how safe any individual API is to call.
Mutable is not part of the Java meta-language used for typing.
ImmutableMap implements Map
https://guava.dev/releases/23.0/api/docs/com/google/common/c...
and throws exceptions on mutation.
https://guava.dev/releases/23.0/api/docs/src-html/com/google...
That seems like a good thing. If you're handed a map to const values you can't just go "imma gunna mutate them anyway".
Casting const away and then mutating is a footgun as you are in undefined behavior territory as soon as your pointer/reference is not just const itself but points to a const variable.
std::unordered_map<std::string, const std::string> m;
m["foo"] = "bar";
std::unordered_map<std::string, std::string> m2 = m;
doesn't compile.Or if you (or something you call) mutate it trough another reference/pointer. Sometimes I wish there was a stronger const where the compiler (and programmer) could actually assume the object is not mutated while that reference/pointer is alive. Of course checking that is no longer doable with just the type system.
> You can't even easily copy your way out.
That depends on the type. std::unordered_maps could provide a copy constructor that does the conversion.
And you can do the copy via iterators, although that is probably not as efficient as it could be because it needs to re-hash the keys.
std::unordered_map<std::string, std::string> m2(m.begin(), m.end());I'm unfamiliar with this rule (and not finding anything good to google). Can you elaborate?
I can't really think of a scenario where an immutable datastructure isn't a subset of actions against a mutable datastructure.
> History constraint (the "history rule"). Objects are regarded as being modifiable only through their methods (encapsulation). Because subtypes may introduce methods that are not present in the supertype, the introduction of these methods may allow state changes in the subtype that are not permissible in the supertype. The history constraint prohibits this. It was the novel element introduced by Liskov and Wing. A violation of this constraint can be exemplified by defining a mutable point as a subtype of an immutable point. This is a violation of the history constraint, because in the history of the immutable point, the state is always the same after creation, so it cannot include the history of a mutable point in general. Fields added to the subtype may however be safely modified because they are not observable through the supertype methods. Thus, one can define a circle with immutable center and mutable radius as a subtype of an immutable point without violating the history constraint.
[1]: https://en.wikipedia.org/wiki/Liskov_substitution_principle#...
This is also a violation of LSP and the open close principle.
Consider a `List` with an `add` function. What would you do with that `add` function to make an `ImmutableList` subtype?
But still it doesn't look very nice in practice. I think that in that case, it does make much more sense for a mutable type to be a subtype of the immutable one.
The immutable threw me off track. It's just that the supertype would lack mutation operations. (in a sense immutable would be the default for every type).
Thank you.
Conceptually, you can have constness in your subtype-system (as long as you are sticking with interfaces (methods), as Liskov's subtyping model does, and aren't inherting potentially-mutable fields).
MutableMap and ImmutableMap are both subtypes of a hypothetical ReadableMap. ImmutableMap is the same as ReadableMap, but has an informal contract that subclasses shouldn't add mutability.