-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Demo: adding m[x] notation for indexing #9174
base: master
Are you sure you want to change the base?
Conversation
CT Test Results 2 files 96 suites 1h 9m 8s ⏱️ Results for commit f47e1ec. ♻️ This comment has been updated with latest results. To speed up review, make sure that you have read Contributing to Erlang/OTP and that all checks pass. See the TESTING and DEVELOPMENT HowTo guides for details about how to run test locally. Artifacts// Erlang/OTP Github Action Bot |
This is something I have wished for for a long time, thanks ❤️ I have some issues with the terminology and syntax, though 😅 The term "index" seems wrong to me in the context of maps, and should be "key" or something. The grammar token should be something like The proposed syntax of ...
A
[X]
[Y]. It's not really obvious at a glance what this means. Each line on its own is valid syntax. Which also means, if a stray comma sneaks in, like this... ...
A
[X],
[Y]. ... it won't be detected by the compiler, since it is all totally valid. But the result won't be what you want at all: My suggestion is, at least put in a |
I don't dislike the proposed notation, but I agree with Maria. This is a record notation: -record(foo, {bar}).
rec() ->
Rec = #foo{bar = bar},
Rec#foo.bar. I suggest using a notation like the one on record: map() ->
Map = #{foo => bar}.
#Map.foo. To me, this notation feels more obvious that I'm getting a map value instead of Note that the below are an invalid record notations: invalid_rec_1() ->
Foo = foo,
Rec = #foo{bar = bar},
Rec#Foo.bar.
invalid_rec_2() ->
Foo = foo,
#Foo{bar = bar}. |
The act of looking up something is commonly referred to as indexing, and the terminology should also not be too specific to the particular application of looking up from a dictionary, since it could be made to apply to things like tuples or binaries as well.
This is true, but the same goes for Python, Javascript, Ruby, etc. They all have the same possibility of accidentally leaving out a comma, but I don't see many people pointing this out as a big problem. The advantages of using a universally recognized notation compared to adding yet another Erlang specific quirk should outweigh the risk of occasional typos, I think. |
This is kinda ok (though I can't say I like it much, sorry to say) as long as the key is an atom. It gets pretty weird when the key is something else: |
This doesn't mean it is good, just saying 😜
Let's just say I really disagree on that point 😅 Curious to see what others think... |
Keep in mind that both map and key can be computed values. You probably don't want this:
|
Oh, and don't forget that you probably want to be able to chain them in a good way. Consider:
compared to
|
Really? I would say that the common expression is "looking up (by index, by key, by ...)". English is not my native language, though, so I may be mistaken.
I think this should be restricted to maps. Because aside from maps, what this could be applied to are:
|
That's perfectly fine, but the (suggested) notation is neutral and shouldn't be hard coded to maps only, because we don't know what we might want to add in the future. Maybe built-in arrays or other kinds of vectors as an example. |
Or somekind of Access protocol similar to what Elixir uses :) So that we could use it for custom datatypes. |
Yes, it's an interesting question if that can be added to Erlang in a nice way (with little overhead and no complications at compile time and supporting dynamic code updates and with good debugging support). |
(Disclaimer: This is just my personal opinion) Please don't introduce multiple mostly-alike-but-oh-so-slightly-different implementations of the same thing. Also, please don't introduce "magic wands" that work on anything remotely alike except when it doesn't. This is one thing I hate most about how Clojure (what I do for a living) handles things, and what I cherish most about how Erlang handles things. Like, lists and vectors in Clojure. Basically the same thing from an outside (user) perspective; except when you eg
The Clojure way of "pick your best fit of a variety of similiar data structures" and "one function to work on every data structure" may seem nice and shiny at first. Until you get to debugging things, and all your logic is fine, but different things happen because the data running through it is of one type and not another, or got silently transformed from one type into a another, one that can be (and is) processed, just with slightly different results. |
So do I, wholeheartedly! I think that fitting a new feature/syntax into a language should, above all, be guided by the specifics (or quirks, if you want) of the language it is introduced into, not by what it looks like in other languages that already have that feature. What you will get by adopting the latter approach is a very messy mish-mash conglomerate of syntaxes borrowed from all over the place. "$THIS has to be done the tl;dr, my 2ct: We are doing Erlang, so let's just keep doing things the Erlang way. There are enough things for newcomers to get used to, not only syntax, that is IMO a minor part. Learning a different but not too far off access syntax is just another small thing on the learners side to overcome. Using a construct that does not fit into the overall language is a permanent sore once you're into the language. |
tl;dr, I totally agree with what @juhlig said. To illustrate the point of "works on everything, except when it doesn't" in the current access syntax context, imagine the following: M = #{a => b, 1 => c}.
L = [b, a].
By using the same access syntax on different things, you never know what it is that you will get. To be sure, you have to use type checks ( |
I agree that this kind of situation is not what you want, and thanks for sharing your Clojure experiences. Like I said, debugging must work well. On the other hand there are situations where you do want to be able to run the same nontrivial piece of code on different implementations of a data type, and you do not want to maintain several copies of that code each tailored to use a separate implementation. Today, you can solve this using preprocessor macros, or passing around module names for dynamic calls, or by parse transforms or similar code generation approaches, and all those are mostly worse for debugging. It would be nice to have a general built-in approach with good support for tracing and debugging. |
Protocols can be a source a great confusion, but also reduce the amount of code written and make things more performant. For example being able to let the ** (ArgumentError) the Access module does not support accessing lists by index, got: 1
Accessing a list by index is typically discouraged in Elixir, instead we prefer to use the Enum module to manipulate lists as a whole. If you really must access a list element by index, you can Enum.at/1 or the functions in the List module
(elixir 1.17.3) lib/access.ex:347: Access.get/3
iex:2: (file) I doubt that we will ever get protocols in Erlang, but at times I think we really are missing out. The combination of protocols and structs in Elixir is very neat IMO. |
I agree that addition of a map-key access syntax would be a good addition and it would allow simplifying quite a bit of code. I'm not convinced about the The original map EEP proposed I agree with the premise of this discussion about using Polymorphism is a tool with advantages and disadvantages - traditionally Erlang had fairly little polymorphism, and in places where it had it - notably |
32cbe09
to
f15f68b
Compare
f15f68b
to
f47e1ec
Compare
This is just a proof of concept as a discussion point. We could easily have
m[x]
notation for looking up a value in a map (and possibly other data types), if we wanted to. It's no different grammatically from how it works in e.g. Python or Javascript.As people are using maps more and more, I see that there could be a case for adding this syntax now, as an alias for
erlang:map_get/2
, especially for accessing nested maps. Works in guards as well.