[swift-evolution] [Pitch] Add `mapValues` method to Dictionary

Honza Dvorsky jan.dvorsky at me.com
Sat May 21 05:27:10 CDT 2016


Hello everyone,

I have added a very simple, but powerful method into a Dictionary extension
on multiple projects in the last weeks, so I'd like to bring up the idea of
adding it into the standard library, in case other people can see its
benefits as well.

Currently, Dictionary conforms to Collection with its Element being the
tuple of Key and Value. Thus transforming the Dictionary with regular map
results in [T], whereas I'd find it more useful to also have a method which
results in [Key:T].

Let me present an example of where this makes sense.

I recently used the GitHub API to crawl some information about
repositories. I started with just names (e.g. "/apple/swift",
"/apple/llvm") and fetched a JSON response for each of the repos, each
returning a dictionary, which got saved into one large dictionary as the
end of the full operation, keyed by its name, so the structure was
something like

{
  "/apple/swift": { "url":..., "size":...., "homepage":... },
  "/apple/llvm": { "url":..., "size":...., "homepage":... },
  ...
}

To perform analysis, I just needed a dictionary mapping the name of the
repository to its size, freeing me to discard the rest of the results.
This is where things get interesting, because you can't keep this action
nicely functional anymore. I had to do the following:

let repos: [String: JSON] = ...
var sizes: [String: Int] = [:]
for (key, value) in repos {
  sizes[key] = value["size"].int
}
// use sizes...

Which isn't a huge amount of work, but it creates unnecessary mutable state
in your transformation pipeline (and your current scope). And I had to
write it enough times to justify bringing it up on this list.

I suggest we add the following method to Dictionary:

extension Dictionary {
      public func mapValues<T>(_ transform: @noescape (Value) throws -> T)
rethrows -> [Key: T] {
        var transformed: [Key: T] = [:]
        for (key, value) in self {
            transformed[key] = try transform(value)
        }
        return transformed
    }
}

It is modeled after Collection's `map` function, with the difference that
 a) only values are transformed, instead of the Key,Value tuple and
 b) the returned structure is a transformed Dictionary [Key:T], instead of
[T]

This now allows a much nicer workflow:

let repos: [String: JSON] = ...
var sizes = repos.mapValues { $0["size"].int }
// use sizes...

and even multi-step transformations on Dictionaries, previously only
possible on Arrays, e.g.
var descriptionTextLengths = repos.mapValues { $0["description"].string
}.mapValues { $0.characters.count }

You get the idea.

What do you think? I welcome all feedback, I'd like to see if people would
support it before I write a proper proposal.

Thanks! :)
Honza Dvorsky
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160521/3dce23aa/attachment.html>


More information about the swift-evolution mailing list