# Non-inplace \`map\` for Dictionaries

**URL:** <https://discourse.julialang.org/t/non-inplace-map-for-dictionaries/22593>\
**Category:** Internals & Design\
**Tags:** question, dictionary\
**Created:** [April 1, 2019, 10:37am UTC](https://discourse.julialang.org/t/non-inplace-map-for-dictionaries/22593 "2019-04-01T10:37:49Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![simeonschaub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simeonschaub/32/216566_2.png) [@simeonschaub](https://discourse.julialang.org/u/simeonschaub)\
**Post date:** [April 1, 2019, 10:37am UTC](https://discourse.julialang.org/t/non-inplace-map-for-dictionaries/22593/1 "2019-04-01T10:37:49Z")

</div>

I am currently implementing a custom `Dict` type, which allows for different key and value types. This means that `iterate` is not type stable, but many operations like `getindex`, `merge` or `map` can be implemented to be type stable. I was looking for a way to implement `map` only to find that it isn’t implemented for dicts anymore. I stumbled upon the discussion on [this](https://github.com/JuliaLang/julia/pull/31223) PR where it was decided against adding a non-inplace `map` for dicts. @stevengj brought up the following argument:

> I think the current semantics of `map(f, values(dict))` producing a new array of values is fine and is what I would expect from mapping the `values` iterator, consistent with other iterators. We already have a non-mutating dictionary map via `Dict(k => f(v) for (k,v) in d)` , and you could also do `map!(f, values(copy(dict)))` to avoid re-hashing the keys, so I’m not sure there is a need for a new function.
> 
> In contrast, `map!(f, values(dict))` , which is currently an error, has only one possible reasonable meaning to me: modify the values of `dict` in-place.
> 
> The main value of this function is as a performance optimization in the in-place case, anyway; in a typical context where you are willing to make a copy of the dictionary, I’m guessing that the performance cost of re-hashing the keys is not such a big deal.

Both alternatives, `Dict(k => f(v) for (k,v) in d)` and `map!(f, values(copy(dict)))` rely on `iterate` and therefore don’t make sense for certain `AbstractDict`s like mine. I would also argue that something along the lines of `map(f, dict)` is much more concise than both alternatives and I think many users would expect that to work since `NamedTuples` already work that way. It would also make it easier to write generic code for different types of collections.  
I can’t really see the benefit in not having such a function. One might still want to discuss, whether the function should have a different name and whether `f` should take in a `Pair` of key and value or just the value. Those are just my thoughts and use case though, feel free to discuss.  
Thank you for making Julia so awesome!

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [April 1, 2019, 12:55pm UTC](https://discourse.julialang.org/t/non-inplace-map-for-dictionaries/22593/2 "2019-04-01T12:55:49Z")

</div>

I don’t understand what your problem with `map!(f, values(copy(dict)))` is.

It is assumed that `AbstractDict` supports iteration. If your data type cannot support iteration, then you should consider choosing a different abstract supertype (most likely candidate: none at all). Of course it is sometimes necessary to be pragmatic, and break assumptions.

Can you give a more concrete description of your datastructure and use-case?

---

<div class="post-metadata">

**Author:** ![simeonschaub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simeonschaub/32/216566_2.png) [@simeonschaub](https://discourse.julialang.org/u/simeonschaub)\
**Post date:** [April 1, 2019, 2:20pm UTC](https://discourse.julialang.org/t/non-inplace-map-for-dictionaries/22593/3 "2019-04-01T14:20:14Z")

</div>

My dict basically consists of a tuple of different `AbstractDict`s, all with different key types that can have different value types. At its heart, my implementation looks like this:

```julia
struct MixedKeyDict{T<:Tuple} <: AbstractDict{Any,Any}
    dicts::T
end

Base.length(d::MixedKeyDict) = sum(length, d.dicts)

function Base.iterate(d::MixedKeyDict, state=(1,))
    index = first(state)
    res = iterate(d.dicts[index], Base.tail(state)...)
    if res == nothing
        if index < length(d.dicts)
            return iterate(d, (index+1,))
        else
            return nothing
        end
    else 
        return first(res), (index, Base.tail(res)...)
    end
end

Base.getindex(d::MixedKeyDict, key) = _getindex(d.dicts, key)

_getindex((d,)::Tuple{D,Vararg}, key::K) where {K,D<:AbstractDict{K}} = d[key]
_getindex(dicts, key) = _getindex(Base.tail(dicts), key)
_getindex(::Tuple{}, key) = throw(KeyError(key))

```

As you can see, it’s not possible to make `iterate` type stable, but `map`, for example could simply be written like this:

```julia
Base.map(f, d::MixedKeyDict) = MixedKeyDict(map.(f, d.dicts))

```

---

<div class="post-metadata">

**Author:** ![ndinsmore](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ndinsmore/32/7433_2.png) [@ndinsmore](https://discourse.julialang.org/u/ndinsmore)\
**Post date:** [April 1, 2019, 3:55pm UTC](https://discourse.julialang.org/t/non-inplace-map-for-dictionaries/22593/4 "2019-04-01T15:55:57Z")

</div>

The general idea is that if someone implements an `abstractdict` then have two options, make sure the keys a values iterators work then the niave implimentation of map! in abstract dict should work.

If they want to have a more performant version they can implement their own implementation of `map!`. In your case I think that would look like:

```nohighlight
function Base.map!(f,iter::Base.ValueIterator{<:MixedKeyDict})
    for d in iter.dict.dicts
          map!(f,values(d))
    end
    return iter
end

```

(I didn’t check that code)

That should be just as fast as `map!` for a normal `Dict`
