This module aims to provide functions that perform operations specific to maps (like accessing keys, updating values, and so on). For traversing maps as collections, developers should use the Enum module that works across a variety of data types.
The Kernel module also provides a few functions to work with maps: for example, Kernel.map_size/1 to know the number of key-value pairs in a map or Kernel.is_map/1 to know if a term is a map.
Correct. Kernel was used to be bigger but we shrunk it before 1.0. The consensus was to keep those in Kernel precisely because they are the only ones available in guards.
One thing to consider is that even though Map.fetch!/2 and map_get/2 would have the same functionality, they are slightly different from the VM perspective. Most notably, Map.fetch!/2 is a regular function, so it can be traced, while map_get/2 is a guard BIF, so it cannot be traced. Additionally, since Map.fetch!/2 is a regular function, it requires setting up a stack in the caller and is potentially slightly more expensive.
For now, we will go with extending defguard/1 (discussion #2) and adding the is_struct and is_exception guards. For the remaining ones, we will wait until more use cases arrive, if ever.
Note though those enhancements will take a while to arrive, since they require Erlang/OTP 21, and the first Elixir release that will support exclusively Erlang/OTP 21+ is likely 1.10, which is about 1.5 years from now.
@josevalim ah, my bad then
On github I can see only 1.7.0, 1.8.0 and 2.0.0 milestones, so I assumed you mean 2.1.0 as 1.8.0 therefore would be last 1.x.y release.
Discussion #1: Should we add map_get/2 and is_map_key/2 to Kernel?
No. But for a different reason than what other people have been saying:
I think it is important to note here that the opposite is already happening: There exist some non-Kernel functions that are allowed in guards (Integer.is_odd and Integer.is_even come to mind; maybe there are others I cannot think of right now).
So I think that the most consistent way ( Where consistent is meant as: āKeeping in line with current decisions made in the standard libraryā) forward would be to alter the implementation of Map.fetch! and Map.has_key? as delegating to the :erlang.map_get and :erlang.is_map_key guards, meaning that Map.fetch! and Map.has_key? can be used inside guards.
They live outside of the Kernel for the same reason as Integer.is_odd does: People will not use them that often (we want them to usually use pattern matches instead).
I donāt think @michalmuskala 's concern of them not being traceable would be a problem, because these functions are never used in isolation.
Discussion #2: Should we allow patterns in defguard/1?
Yes, Definitely. This would make writing guards a dream!
About the performance problems: It seems to me that people might be complecting multiple things:
Speed of compilation. Which, unless extremely unreasonably slow, should not be that big of an issue.
Speed of runtime. My question to you: Would this be faster or slower than the current state of affairs? (Where non-guard constructs like ācondā are used to fake struct lookup)
Discussion #3: Should we allow map.field in guards?
No. Just like some other people have already said, the āfreedom of syntaxā that this gives people can be considered harmful; there are other, more explicit ways to do these tests.
This is different because those are implemented on top of guards. They are literally macros. It can be implemented by anyone, anywhere, anytime. It is different from adding map_get and has_key which would be new primitives.
To be more precise, you need to require Integer before using Integer.is_odd/2. Imagine now if we had to require Map before using Map.fetch! or Map.has_key?? For starters, it would be backwards incompatible.
#1 - After reading Michalās argument Iām inclined to say yes too, but at the same time the thing Iām still not sure, although I know Iāve tried to write variants of def func(key, %{key => value}), is if thereās any situation where you wouldnāt instead be explicit about the keys - i.e. youāll end up having to differentiate/pattern match on the possible values that key will assume either way?
#2 - I donāt particularly use defguards so I donāt really have much of an opinion here, I see many people do, so I would assume itās something wanted and useful (and perhaps I will use them in the future too) and it also makes it more consistent to be able to use pattern matching everywhere.
#3 - Not really sure - It looks clean in isolation but it would be a replacement for explicit pattern matching, and I think that pattern matching is the idiom that is more consistent and clean overall, and because it only works with atom keys I donāt think it makes that much sense and can add to confusion when using it. In JS land thereās no atoms, so you can switch the access syntax at will when writing, and there it makes a lot of sense. Also, if thereās the opportunity to write it in guard clauses you can possible end up with moving all of those checks to the guard part of the function making the code less readable - at the same time, if you just want to discern between a key value being different (!=) from a certain value you would always write a pattern match on the key, and then a guard for that assertion either way, so it would be cleaner to just write a guard that handled both in one expression. I donāt see a need for it behind āhypotheticalā situations so Iām saying noā¦
Either way, I would be totally fine with all of those changes if they were introduced, I also donāt think thereās much too loose by introducing flexibility to the language (other than didactically), these donāt seem to be dramatic changes but instead improvements on the current syntax that go hand in hand with the latest introductions to OTP21, but thereās nothing wrong with having artificial constraints that lead to better written code either.
I am still thinking No for the first question (adding the guards to the Kernel), because removing something is a lot harder than adding something later if it turns out suitable use-cases exist. And until that time, people that really need it can use the versions exposed by :erlang instead.
Discussion #1: Should we add map_get/2 and is_map_key/2 to Kernel?
no
This is unnecessary since there is already a generic way to obtain the same result using defguard/1.
Discussion #2: Should we allow patterns in defguard/1?
yes, for the reason above
Discussion #3: Should we allow map.field in guards?
no, if we go for defguard/1
Generally it seems like a better idea to have one way to have custom guards using defguard/1. Another argument would be: if another guard gets added to erlang, they will have to be added to Elixir as well, and maybe this will not be necessary but still will break consistency.
I have a few more thoughts for making the documentation more intuitive for those new to Elixir, which Iāll link to as a separate thread when I have the spare time.
Related to the guards discussion, Elixir master use the new documentation metadata to annotate when we have guards. We are leveraging this information to show Guards in their own section in the docs sidebar: https://hexdocs.pm/elixir/master/Kernel.html