Why Keyword.drop/2 and Map.drop/2 instead of Enum.drop/2?

I am currently building an adapted API for Elixir…

And I just noticed how many functions of the same name Keyword and Map share.

What I’m wondering is why both the functions were put there instead of just in Enum?


My first guess would be: optimization.

And so my real question is from a design standpoint:

What determines whether a function should go in Enum?


P.S. I know Enum.drop/2 exists–BUT, it actually does something different.

P.P.S. Whatever the answer is belongs in the docs.

An initial thought:

Imagine actually using Enum.get/3 While it’s doable, to me, it’s counter-intuitive – a dev wants to know what is the data structure being gotten from.


By contrast, Map.reduce/2-3 and Keyword.reduce/2-3 would suck because the operation has a different goal. It works on all entries of the collection; it doesn’t just fetch one.

To add:

drop/2 operates on some but not all entries.

I think that’s the design distinction.

Most of the contributions I’ve made to the Elixir core code have been contributions to the docs so once you get an answer, I encourage you to fork the Elixir core code and add what you need.

2 Likes

Keyword and Map are Enumerable in the sense that you can sensibly go through each entry one at a time. However they are also key value data structures. There will be a variety of same name functions related to the fact that they are both key value data structures that have nothing to do with iterating through collections.

Enum does not exist as a one stop shop of “every function that applies to multiple data structures”. It is specifically about manipulating Enumerable structures in their capacity of being enumerable.

5 Likes

If you want to create a function that covers Keyword.drop/2 and Map.drop/2 you would need to create something like Enum.drop_by_key/2. The thing is that it will not work for Enumerables that don’t have a key/value structure (as @benwilson512 said).

Remember maps are unordered data structures so many of the functions related to order in the Enum module will give unexpected results in maps and structs, such as Enum.drop/2 or Enum.take/2

You may want to have a look at this project of mine

It may be useful for what you are working on

1 Like

Apart from everything that has already been said, I also think it’s a “syntax vs semantics” issue - that is, drop as a name plays the role of syntax while the module name adds context to define the desired semantics.

iex(1)> kw = [a: 1, a: 2, b: 3, c: 4, a: 5]
[a: 1, a: 2, b: 3, c: 4, a: 5]
iex(2)> m = Map.new(kw)
%{a: 5, b: 3, c: 4}
iex(3)> Enum.drop(kw,2)
[b: 3, c: 4, a: 5]
iex(4)> Enum.drop(m,2)
[c: 4]
iex(5)> Keyword.drop(kw, [:a])
[b: 3, c: 4]
iex(6)> Map.drop(m, [:a])
%{b: 3, c: 4}
iex(7)>
  • Enum.drop/2 operates in an enumerable fashion i.e. drop the first two elements.

    • The first two element for Keywords is deterministic.
    • The first two elements are not determistic for Maps. Also the output is a list - not a Map. So Enum.drop/2 is not a good fit for Maps.
  • Keyword.drop/2 and Map.drop/2 are keyed drops.

    • While Keywords are often used to create Maps, they aren’t equivalent because Keywords can contain duplicate keys which Maps cannot.
    • Keyword.drop/2 is expected to return Keywords
    • Map.drop/2 is expected to return a Map

So from that perspective it is entirely appropriate that these implementations of drop exist in different places (contexts).