Hi all! Just want to know if there is someone else thinking Map.take
is not a very good name for function that returns subset of a map. It’s not an issue or something but I feel uncomfortable with that name. In ruby, for example, method with same functionality called slice
. When I type Map.take([:a, :b])
I expect a list of values mapped to these keys. What do you think?
You mean you would expect it to do something like this?
Enum.map(keys, fn key ->
case Map.fetch(map, key) do
{:ok, value} -> {key, value}
_ -> nil
end
end)
Perhaps, but I think that the name take
is not obviously indicating one or the other case, so I personally don’t think it’s a bad name. Maybe it could have been called Map.pick
, just to differentiate it from Enum.take
which takes a number of elements, but renaming it now would have much bigger disadvantages than advantages in my opinion.
I expect it like this:
take = fn map, keys ->
Enum.map keys, & Map.get(map, &1)
end
a = %{a: 1, b: 2, c: 3}
take.(a, [:b, :c])
> [2, 3]
Well, I don’t think it has to be renamed. Just want to know what other people think about it.
I find it quite understandable. I’ve always found slice
hard to remember, even after many years of Ruby
My point is that you take values
from map. I think, slice
is not ideal either. But in my opinion, it is better that take
.
You also take a subset
. Language is tricky
The name take
seems very natural to me. What you describe there could be done with %{a: 1, b: 2, c: 3} |> Map.take([:b, :c]) |> Map.values()
(provided ordering is not important).
However, I have wanted a Map.values/2
that has an extra parameter to name which keys’ values you want.
Yes, I can, but I also can do it with several other ways. But oneliner is much prettier that 2 pipes.
That’s a very good idea!
Since Elixir already has Map.values/1
it could be a little effort to make that function.
You want a one-liner? You asked the right person
def values(%{} = map, keys) when is_list(keys), do: for {k, v} <- map, k in keys, do: v
Like a charm
FYI, I’m 90% sure that map |> Map.take(keys) |> Map.values
is gonna win from a performance perspective because those are both BIFs.
Just that would not guarantee that the order of the values is the same as keys
.
Idk I personally like the name and found it intuitive when I first heard of it.
Good point! I’m actually not sure what the expectation of a Map.values/2
would be now that you mention it. Map.values/1
makes no particular guarantee about value order. I suppose Map.values/2
is a lot more useful though if it does do things in the order of the supplied keys.
OK, guys. As far as I understand everyone is satisfied with name take
. What about values/2
? Do I get it right that there will be a proposal to include that function?
I’m pretty happy with Map.take (and I use it all over the place). At least it’s consistent with Keyword.take.
My only tangentially-related wish is that Map.merge/2
let you use a keyword-list.
You can use Enum.into
for this:
iex(1)> [a: 1, b: 2] |> Enum.into(%{c: 3})
%{a: 1, b: 2, c: 3}
I like values/2
. There will only be a proposal if someone makes one though. The official place to make proposals like this is the elixir-lang-core mailing list here Redirecting to Google Groups
You’ll want to reference this thread as a place where the initial conversation took place and some support for the idea found.
You can’t enum.into in a pipeline the same way
I think a lot of what will influence whether someone finds the vocabulary intuitive will be based on experience in other languages. For instance, slice
wouldn’t likely make sense to someone coming from JS, as that returns a list using index and length as arguments (same as Enum.slice/2
).
In other languages, I have seen take()
used exclusively in the context of lists; with a different method used for collections (eg. pick
or pluck
). Essentially, the deciding factor of take
vs pick
is whether the response includes keys.
In Elixir, take
seems to be used for consistency, with the response being contextual (but matching the original type). For instance:
-
Enum.take
returns a list with no keys -
Keyword.take
returns a keyword (list with keys) -
Map.take
returns a map
If Elixir were to apply the "take
for no keys, and pick
for keys" convention, then it might look like this:
Enum.take/2
returns a list with no keys
Enum.take(["foo", "bar", "baz"], 0)
# ["foo"]
Keyword.take/2
returns a list with no keys
Keyword.take([foo: "bar"], [:foo])
# ["bar"]
Keyword.pick/2
returns a keyword list with keys
Keyword.take([foo: "bar", baz: "qux"], [:foo])
# [foo: "bar"]
Map.take/2
returns a list without keys
Map.take(%{foo: "bar", baz: "qux"}, [:foo])
# ["bar"]
Map.pick
returns a map with keys
Map.pick(%{foo: "bar", baz: "qux"}, [:foo])
# %{foo: "bar"}
I think I could get behind that, but it can also be a little tricky since you’re not always getting back the same type that you started with (losing the consistency that the current implementation provides).
Remember, the hardest part of writing code is knowing what to name things!
One thing I’m realizing is underspecified about values/2
: What should it do if you pass it a key that isn’t in the map? Raise? Return nil? Ignore it?