add Keyword.take!/2, Map.take!/2

139 views
Skip to first unread message

DaTrader

unread,
Dec 18, 2023, 5:56:11 AM12/18/23
to elixir-lang-core
Many times I need to merge a subset of a keyword list or a map into another keyword list or map while requiring it to fail if not all keys are present.

For this purpose I have small utility modules called KwUtils and MapUtils where I have functions like the one below:

```
  @doc """
  Takes specified key-value pairs from the keyword list but raises if any not found.
  """
  @spec take!( keyword(), [ atom()]) :: keyword()
  def take!( kw, keys) do
    taken = Keyword.take( kw, keys)

    if length( taken) != length( keys) do
      raise "Expected all keys in #{ inspect( keys)} to be present in #{ inspect( kw)}"
    end

    taken
  end
```

It'd be nice if a corresponding function was part of the `Keyword` and `Map` modules each.

Rogério Santos

unread,
Dec 27, 2023, 9:21:50 PM12/27/23
to elixir-lang-core
I don't see such special use cases as good candidates to be included in builtins libraries.

DaTrader

unread,
Dec 28, 2023, 4:40:07 AM12/28/23
to elixir-lang-core
It's not special at all. Actually it is as generic as the current `Keyword.take/2`. You either want the operation to succeed if there aren't all keys present or you want it to fail. Those are two equally important use cases.

Artur Plysiuk

unread,
Feb 21, 2024, 4:59:52 AMFeb 21
to elixir-lang-core
I just came here to add the same proposal.

четвер, 28 грудня 2023 р. о 11:40:07 UTC+2 DaTrader пише:

José Valim

unread,
Feb 21, 2024, 6:21:56 AMFeb 21
to elixir-l...@googlegroups.com
+1 for the proposal, but it has to be implemented more efficiently than what was described here and we also need to add drop!. PRs welcome.

--
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/787ccf87-a103-4271-9d77-2101f1e23247n%40googlegroups.com.

DaTrader

unread,
Feb 21, 2024, 6:25:36 AMFeb 21
to elixir-lang-core
@josevalim Can you provide a hint on how to do it more efficiently other than comparing the two lengths - just an idea or where to look to?

sabi...@gmail.com

unread,
Feb 21, 2024, 6:30:25 AMFeb 21
to elixir-lang-core
For the record this proposal has been rejected once in the past https://groups.google.com/g/elixir-lang-core/c/cmirsH_OHKo/m/j2Um5uy-BgAJ

Damir Petkovic

unread,
Feb 21, 2024, 6:37:20 AMFeb 21
to elixir-l...@googlegroups.com
Thanks for sharing but I can't find where exactly it said it was rejected. Seems as if an incomplete discussion without a conclusion or am I missing something?

You received this message because you are subscribed to a topic in the Google Groups "elixir-lang-core" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elixir-lang-core/7jRfrcPBK_w/unsubscribe.
To unsubscribe from this group and all its topics, 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/f09ac2cf-b38d-4035-871e-372d19c742c3n%40googlegroups.com.

José Valim

unread,
Feb 21, 2024, 6:42:05 AMFeb 21
to elixir-l...@googlegroups.com
The previous discussion said:

> Now I am thinking it may be better to not add `Map.take!/2`. It is not complicated to implement it yourself and given the possible confusion with pattern matching and that it may require both drop! and delete!, it is probably not worth it. So for now I would like to postpone adding this functionality. Sorry.

Sabiwara, since you proposed the previous time, what are your thoughts 3 years later?

sabi...@gmail.com

unread,
Feb 21, 2024, 7:45:27 AMFeb 21
to elixir-lang-core
I’ve been thinking about this one for a while and I’m still a bit hesitant.

Most of the time I wish for this I’m actually dealing with static keys, and despite being non-optimal the take syntax is much more compact than the pattern-matching version and has much less repetition:

Enum.map(rows, &Map.take!(&1, [:my_long_field, :another_long_field]))

vs:

Enum.map(rows, fn %{my_long_field: my_long_field, another_long_field: another_long_field} ->
  %{my_long_field: my_long_field, another_long_field: another_long_field}
end)


In the absence of Map.take!/2, I sometimes still default to Map.take/2 just for the readability even if I’d like to fail on missing keys. So Map.take!/2 would be an improvement to at least avoid silent failures.

A map_take!/2 macro could generate optimal code for static keys and keep the conciseness (best of both worlds), but I don’t think there’s a place where it fits well (the Map module has no macros, and it would be weird in Kernel perhaps?).

Another way here could be the field punning feature which is frequently discussed (example: https://groups.google.com/g/elixir-lang-core/c/P6VprVlRd6k/m/q4Jaq49eAgAJ).

Enum.map(rows, fn %{my_long_field, another_long_field} ->
  %{my_long_field, another_long_field}
end)


TLDR to answer your question Jose: yes I still think Map/Keyword take!/drop! would be useful additions, since the shortcomings also apply to Map.take/2.
But I suspect many actual usages could have a better solution.
Reply all
Reply to author
Forward
0 new messages