Why no Dictionary to Dictionary map?

Many types can be mapped over, for example: Array, Dictionary and Optional.


Other examples could be Result, Future and Signal, and all kinds of list- and tree data structures. It's clear that there are a lot of types for which it makes sense to have map defined.


Let's call all such types Mappable types. Any Mappable type can be seen as a type that is wrapping another type:

Arrays are wrapping their Element type,

Optionals are wrapping their Wrapped type,

Dictionaries are wrapping their Element = (Key, Value) type, and so on.


Calling the map method of such a Mappable (wrapping) type simply means that we are applying a transform to its wrapped value(s) and get another instance of the same Mappable type back, now wrapping the new (transformed) value(s) which might be of the same or a new wrapped type. But note that the Mappable (wrapping) type itself is the same.

Put more succinctly:

Using a map(transform: T -> U) on an Array<T> produces an Array<U>

Using a map(transform: T -> U) on an Optional<T> produces an Optional<U>, and so on.


Here's an example of mapping an Optional to another Optional, in this case an Optional<String> to an Optional<Int>:

let o1: Optional<String> = nil
let o2: Optional<String> = "foo"
print(o1.map { $0.characters.count }) // nil
print(o2.map { $0.characters.count }) // Optional(3)


And here is an example of mapping an Array to another Array, in this case an Array<String> to an Array<Int>:

let a1: Array<String> = []
let a2: Array<String> = ["one", "two", "three"]
print(a1.map { $0.characters.count }) // []
print(a2.map { $0.characters.count }) // [3, 3, 5]


And here is an example of mapping a Dictionary to another Dictionary, in this case a Dictionary<String, String> to a Dictionary<Int, Int>:

let d1: Dictionary<String, String> = [:]
let d2: Dictionary<String, String> = ["City" : "Los Angeles", "State" : "California"]
print(d1.map { ($0.0.characters.count, $0.1.characters.count) }) // [:]
print(d2.map { ($0.0.characters.count, $0.1.characters.count) }) // [5: 10, 4: 11]


Except this last example won't work out of the box, because the Swift standard library defines Dictionary's map as returning, not a Dictionary, but ... an Array! So to make the example work, we have to write the Dictionary to Dictionary map method ourselves, something like this:

extension Dictionary {
    func map<K, V where K: Hashable>(transform: (Key, Value) -> (K, V)) -> [K : V] {
        var newDictionary = [K : V]()
        for kv in self {
            let newKv = transform(kv)
            newDictionary[newKv.0] = newKv.1
        }
        return newDictionary
    }
}


So, the standard library defines the obvious/conventional map for Array<T> and Optional<T> (ie their map method returns an Array<U> and an Optional<U>, respectively).

But the map method for Dictionary<T> returns an Array<U> instead of a Dictionary<U>, why is this?


And also, why isn't there a Mappable protocol in the standard library?

(I guess a more conventional name for that protocol would be Functor.)

Replies

Probably because map is just a tool for Swift and not a basic building block as in Haskel etc.

Which begs for a follow up question that is very similar to my original post:

Why take a concept like map and slap it on the std lib as a random "tool" instead of incorporating it as a natural part of a coherent whole?


Seems to me that the whole point of protocols in Swift is to make concepts/types/code more coherent, simple, safe and reusable, no?


And as the std lib defines map for both Optionals and Arrays (well CollectionType, SequenceType), and also has a couple of flatMap methods, users who have previous experience with map and flatMap will surely be surprised/disappointed.


So, the follow up questions could also be:

When the standard library already has a map that is almost there anyway, why stop there?

Why take a simple and powerful concept and make it into a complex and less powerful one?


It's almost as if the designers of Swift are just adding these make-believe-happy-go-lucky-pop-versions of well established concepts in computer science (eg map and flatMap) in order to please some perceived demand for "cool FP names", never mind the real implications/implementations/benefits. But of course I can't really believe that this is actually the case (given who the people on the Swift team are). Yet I can't help but feeling a little bit worried. And I'm still looking for an explanation that makes me understand why I have no reason to be worried.


I would prefer if the explanation is not:

"You just have to accept the new popular and therefore objective meanings of words like map and flatMap. These words have now degenerated into something that is messier and less powerful. But people like them better that way, it's easier, they say. That's it."

Considering the heavy problems many people have with fp concepts and lingo the last quoted text is probably exactly Swift's stance on them.

You mean they're like: "Let's help bring about a new Dark Ages of CS!", while the rest of the world are going: "CS Renaissance, Ahoy!"?

> And also, why isn't there a Mappable protocol in the standard library?


It could be that, as discussed on reddit, Swift protocols are not expressive enough to define a general functor type:

reddit.com/r/swift/comments/2xm80a/swifts_lack_of_monads/

I know it is hard to stomache for some: Swift is no fp language. It just has some fp elements, just like Python.

You should file a radar on this. It seems like a straightforward extension that's largely parallel to the existing definition of Dictionary.map() (unless you're hoping to map to an array of two-tuples).

I expected Dictionary.map to return a Dictionary, just like Array.map returns an Array, and Optional.map returns an Optional. I might file a radar, but I was more interested in an explanation of Swift's interpretation of map, which seems to be unconventional.

22769381