Defmap - embed maps into a module for faster/easier lookups. Feedback please

Defmap is a utility which allows you to embed a map into a module for faster/easier lookups.

https://hexdocs.pm/defmap/Defmap.html

2 Likes

Ah that’s cool. :slight_smile:

1 Like

Can you explain me the reason of use macros in your project?
For me (I don’t see a special use case) it’s unnecessary. Instead of this I recommend to use ETS (Erlang Term Storage) or Agent.
For example look at example KV.Bucket implementation.
In my case library do not need to reserve get method name and code is much more cleaner (at least for me).
Your library also does not allow to override Map.
Can you agree with me or I missed something? What do you think about my comment?

Edit
One more example:

defmodule Example do                                                   
  @map %{
    400 => "Bad Request",
    401 => "Unauthorized",
    403 => "Forbidden"
  }

  def get(code), do: @map[code] || :error                                
end

To me it looks like a way to define an immutable enumeration.

Ok, how about my example in edit?

Well, there can be some performance advantages in functional heads vs just looking things up in a map. This is more or less the way unicode support is implemented in Elixir core.

I think I’d like this more if it did not use the Map get as the function name and I’d really like it a lot more if it wasn’t called Defmap. The function is really just get_constant, or even just constant or attribute. It’s a generic tool for exposing module attributes as functions. Map is just a convenient way to structure the input, you aren’t defining a Map in any practical sense at all.

The only thing wrong with this is actually the hardest part of programming, naming stuff. I think what it does is useful.

3 Likes

Thanks for all the feedback guys :slight_smile:

I have benchmarked the simple map implementation from @Eiji

Elixir 1.3.3
Benchmark suite executing with the following configuration:
warmup: 2.0s
time: 5.0s
parallel: 1
Estimated total run time: 14.0s

Benchmarking defmap...
Benchmarking plainmap...

Name               ips        average  deviation         median
defmap          4.87 K      205.17 μs     ±6.77%      201.00 μs
plainmap        4.46 K      224.04 μs    ±11.79%      215.00 μs

Comparison: 
defmap          4.87 K
plainmap        4.46 K - 1.09x slower

The difference is performance is not huge. However, there is a huge difference in memory usage. I checked the memory usage of the defmap version using :observer.start (Please recommend a good memory profiling tool, I didn’t know any :slight_smile: ) and defmap used ~43mb of memory whereas plainmap used ~200mb, This was my initial assumption as for every lookup with the map implementation we are instantiating a full map and then selecting one value from it. You can checkout the test implementation @ https://github.com/minhajuddin/defmap_test

1 Like

Ok, now I see your point.
Thanks for performance testing, they give me a lot to think about.

I have a feature request:
more configuration - allow to send opts like:

defmodule MyProject do
  YourLibrary.your_macro(my_first_map)
  YourLibrary.your_macro(my_second_map, [caller: :get_value])
  YourLibrary.your_macro(my_third_map, [private: true])
end

to allow define multiple embed maps (also private maps) into one module with different callers.
Your default configuration config.exs:

config :defmap, :default_opts,
  caller: :get,
  private: false
1 Like

Thanks for the feedback :slight_smile: and ideas. I’ll add them soon

I’m not sure you need a lib and a macro for this. You can get the same thing with something like this (untested):

defmodule Foo do
  for {code, message} <- {400 => "Bad Request", 401 => "Unauthorized", 403 => "Forbidden"} do
    def get(unquote(code)), do: {:ok, unquote(message)}
  end
  def get(_), do: :error
end

I occasionally use this technique.

6 Likes