As promised, I ran some benchmarks comparing maps:map, Map.new, and for, and was actually pretty surprised by the outcome:
Name ips average deviation median 99th % Map.new 72.78 13.74 ms ±21.75% 13.64 ms 24.06 ms maps:map 62.72 15.94 ms ±5.54% 16.22 ms 18.57 ms for 55.97 17.87 ms ±13.60% 17.45 ms 27.70 ms Comparison: Map.new 72.78 maps:map 62.72 - 1.16x slower +2.20 ms for 55.97 - 1.30x slower +4.13 ms You can find the code for the benchmark here: https://gist.github.com/J3RN/51db9b64e51e83276a7bdb17bc720a92 I ran this benchmark with several different sizes of maps, and the results were consistent from 10 keys to 100,000 keys. Where do we go from here? Given that Elixir's up-front transformation of a Map into a List proved faster than Erlang's iterators, I struggle to imagine how we could write a faster version than what we already have with Map.new. My original goal was to add a function to Elixir that would allow developers to do transformations on maps in a pipeline, and it appears that Map.new fits that niche. However, Map.new but lacks discoverability (IMO) simply due to its name. When encountering the need for this function, I searched for function names such as Map.map, Map.transform, etc, and didn't find what I was looking for. Would a simple alias of, say, Map.transform to Map.new be a worthwhile solution to this problem? On Thursday, January 14, 2021 at 9:35:02 AM UTC-5 José Valim wrote: > But that will make things slower, which is counter to the point of using > :maps.map (inside Map.new) in the first place. :) > > On Thu, Jan 14, 2021 at 3:08 PM eksperimental <eksper...@autistici.org> > wrote: > >> The arity issue can be dealt internally. >> >> def new(map, transform) when is_map(map) and is_function(transform, 1) >> do >> :maps.map( fn key, val -> >> {_new_key, new_value} = transform.({key, val}) >> new_value >> end, >> :maps.iterator(map) >> ) >> end >> On Wed, 13 Jan 2021 23:55:17 -0500 >> Austin Ziegler <halos...@gmail.com> wrote: >> >> > Building `Map.new/2` on `maps:map/2` would be incompatible, because >> > the transformation function differs in arity (`/1` for `Map.new/2` >> > and `/2` for `maps:map/2`). >> > >> > It would be possible to build `Map.new/2` such that it can tell the >> > difference between a `/1` or a `/2` when the first function is itself >> > a map or struct…but I’m not sure that would be an improvement, and it >> > would lead to potentially confusing documentation on when a `/2` >> > could be passed to `Map.new/2` and when a `/1` could be passed to >> > `Map.new/2`. >> > >> > If something is to be done, I believe that the original proposal, >> > surfacing `maps:map/2` as `Map.map/2` with the arguments flipped for >> > pipeline use _might_ be the best choice. At the same time, I’m not >> > _entirely_ sure that would be useful, as most pipelines transform >> > maps into lists and `maps:map/2` borks on a list: >> > >> > ```elixir >> > iex(1)> :maps.map(fn k, v -> v * 2 end, [{:a, 1}, {:b, 2}]) >> > warning: variable "k" is unused (if the variable is not meant to be >> > used, prefix it with an underscore) >> > iex:1 >> > >> > ** (BadMapError) expected a map, got: [a: 1, b: 2] >> > (stdlib 3.12.1) maps.erl:247: :maps.map(#Function<13.126501267/2 >> > in :erl_eval.expr/5>, [a: 1, b: 2]) >> > ``` >> > >> > At this point, even though I am happy to have discovered `maps:map/2` >> > from this discussion, that this would _not_ improve the usability of >> > Elixir on map transformation. >> > >> > A different question: how can we make these rich Erlang functions >> > much more visible to Elixir users like myself? I don’t care that the >> > arguments are “backwards” from the pipeline, because `maps:map/2` is >> > _incredibly_ useful and will improve some code that I have in >> > production. >> > >> > -a >> > >> > On Wed, Jan 13, 2021 at 7:54 PM eksperimental >> > <eksper...@autistici.org> wrote: >> > >> > > Great finding. >> > > Regardless the benchmarks, Map.new/2 should be optimized for maps >> > > using :maps.map/2, because as of now we are doing: map |> >> > > Enum.to_list() |> reduce list |> :lists.reverse() |> >> > > :maps.from_list() >> > > >> > > On Tue, 12 Jan 2021 14:09:26 -0800 (PST) >> > > "jonar...@gmail.com" <jonar...@gmail.com> wrote: >> > > >> > > > This usage of Map.new/2 is news to me (pun somewhat intended) and >> > > > pretty neat! However, I think that the naming alone may make it >> > > > less discoverable and a bit harder to grok when first >> > > > encountered. There also *might* be some performance gains from >> > > > using Erlang's maps:map for this task instead of maps:from_list >> > > > but I'm not sure. I'll do some profiling and find out. >> > > > >> > > > On Tuesday, January 12, 2021 at 5:01:51 PM UTC-5 >> > > > halos...@gmail.com wrote: >> > > > >> > > > > It’s not implemented with maps:map/2, but Map.new/2 should do >> > > > > the trick. >> > > > > >> > > > > `Map.new(map, fn {k, v} -> {k, v * 2} end)` >> > > > > >> > > > > That said, `maps:map/2` is available: >> > > > > >> > > > > `:maps.map(fn _k, v -> v * 2 end, %{x: 1, y: 2, z: 3})` >> > > > > >> > > > > It might be worth exploring whether `Map.map` would be >> > > > > useful/efficient enough to add for piping purposes. >> > > > > >> > > > > -a >> > > > > >> > > > > On Tue, Jan 12, 2021 at 4:51 PM jonar...@gmail.com >> > > > > <jonar...@gmail.com> wrote: >> > > > > >> > > > >> A common task is to iterate over a map performing some >> > > > >> operation thereby producing a new map. There are some ways to >> > > > >> do this in Elixir presently, the simplest probably being >> > > > >> for...into: >> > > > >> >> > > > >> for {key, val} <- map, into: %{} do >> > > > >> {key, val * 2} >> > > > >> end >> > > > >> >> > > > >> Enum.reduce/3 is also an option. However, Erlang provides a >> > > > >> simple function to replace the values of a map with maps:map >> > > > >> function: >> > > > >> >> > > > >> maps:map(fun(Key, Val) -> 2 * Val end, Map) >> > > > >> >> > > > >> I think an equivalent of this in Elixir would be very useful >> > > > >> either as Map.map/2 or Map.transform_values/2 like so: >> > > > >> >> > > > >> Map.transform_values(map, fn {_key, val} -> val * 2 end) >> > > > >> >> > > > >> I'm interested to hear if the community considers this a >> > > > >> worthwhile addition! >> > > > >> >> > > > >> -- >> > > > >> >> > > > > You received this message because you are subscribed to the >> > > > > Google Groups >> > > > >> "elixir-lang-core" group. >> > > > >> To unsubscribe from this group and stop receiving emails from >> > > > >> it, send an email to elixir-lang-co...@googlegroups.com. >> > > > >> To view this discussion on the web visit >> > > > >> >> > > >> https://groups.google.com/d/msgid/elixir-lang-core/d843c44b-e658-4d71-bb66-00c1e0a21ef7n%40googlegroups.com >> > > > >> < >> > > >> https://groups.google.com/d/msgid/elixir-lang-core/d843c44b-e658-4d71-bb66-00c1e0a21ef7n%40googlegroups.com?utm_medium=email&utm_source=footer >> > > > >> > > > >> . >> > > > >> >> > > > > >> > > > > >> > > > > -- >> > > > > Austin Ziegler • halos...@gmail.com • aus...@halostatue.ca >> > > > > http://www.halostatue.ca/ • http://twitter.com/halostatue >> > > > > >> > > > >> > > >> > > -- >> > > You received this message because you are subscribed to the Google >> > > Groups "elixir-lang-core" group. >> > > To unsubscribe from this group and stop receiving emails from it, >> > > send an email to elixir-lang-co...@googlegroups.com. >> > > To view this discussion on the web visit >> > > >> https://groups.google.com/d/msgid/elixir-lang-core/5fff9633.1c69fb81.eec7e.37f2SMTPIN_ADDED_MISSING%40gmr-mx.google.com >> > > . >> > > >> > >> > >> >> -- >> You received this message because you are subscribed to the Google Groups >> "elixir-lang-core" group. >> To unsubscribe from this group and stop receiving emails from it, send an >> email to elixir-lang-co...@googlegroups.com. >> > To view this discussion on the web visit >> https://groups.google.com/d/msgid/elixir-lang-core/6000506f.1c69fb81.6dc1c.59d9SMTPIN_ADDED_MISSING%40gmr-mx.google.com >> . >> > -- You received this message because you are subscribed to the Google Groups "elixir-lang-core" group. To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-core+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/196342fd-3c1d-4af5-80c2-159f46d97b4bn%40googlegroups.com.