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

Tim Vermeulen tvermeulen at me.com
Mon May 23 07:03:53 CDT 2016


I really like this idea, because indeed this wasn’t possible functionally before. I have a small remark though, wouldn’t it be better to let transform be of type (Key, Value) throws -> T instead of (Value) throws -> T? You can just ignore the key (with _) if you don’t need it, but I think it might come in handy in some cases.

> 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
> 
> 
> 
> 


More information about the swift-evolution mailing list