The map method, like most collection methods, is not specifically defined for SortedMap . It is defined in a higher level class ( TraversableLike ) and uses the "builder" to convert the result to the correct return type.
So, how does it determine what the βcorrectβ return type is? Well, he is trying to return you a return type that started as. When you tell Scala that you have a Map[String,String] and ask for its map , then the builder should figure out how to "build" the return type. Since you told Scala that the input was Map[String,String] , the builder decides to build Map[String,String] for you. The builder does not know that you need a SortedMap , so it does not give you one.
The reason it works when you leave an annotation of type Map[String,String] is because Scala indicates that type a is equal to SortedMap[String,String] . That way, when you call map , you call it on SortedMap , and the builder knows how to build a SortedMap to return.
As far as you argue that methods should not alter "fundamental properties", I think you are looking at it from the wrong angle. These methods will always return you an object corresponding to the specified type. This is a type that defines the behavior of the builder, not the base implementation. When you think about it, this is the type that forms the contract for how the methods should behave.
Why do we need this?
Why is this the preferred behavior? Consider a specific example. Say we have SortedMap[Int,String]
val sortedMap = SortedMap[Int, String](1 -> "s", 2 -> "t", 3 -> "u", 4 -> "v")
If I were to map over it with a function that modifies the keys, I risk losing elements when their keys collide:
scala> sortedMap.map { case (k, v) => (k / 2, v) } res3: SortedMap[Int,String] = Map(0 -> s, 1 -> u, 2 -> v)
But hey, that's fine. This is a map after all, and I know this is a map , so I should expect this behavior.
Now let's say that we have a function that accepts Iterable pairs:
def f(iterable: Iterable[(Int, String)]) = iterable.map { case (k, v) => (k / 2, v) }
Since this function has nothing to do with map s, it would be very surprising if the result of this function had fewer elements than the input. In the end, the map on Iterable should display the displayed version of each element. But a map is Iterable pairs, so we can pass it to this function. So what happens in Scala when we do?
scala> f(sortedMap) res4: Iterable[(Int, String)] = List((0,s), (1,t), (1,u), (2,v))
Look at this! No items are lost! In other words, Scala will not surprise us by violating our expectations about how a map on Iterable should work. If instead the builder tried to create a SortedMap based on the fact that the input was a SortedMap , then our function f would have unexpected results, and that would be bad.
So, the moral of this story: use types to talk about the structure of collections, how to handle your data. If you want your code to expect the map to be sorted, you must enter it as SortedMap .