Proposal: Add field puns/map shorthand to Elixir

proposal
on-topic-only-please

#66

I would also like to add that if you don’t like punning, it’s always optional. So for the people that don’t like the style they can still opt to duplicate the destructuring. Even though only less than half of the people voted for this (and I think this is also because of the confusing suffix after the map), there is still a large group of people that really would love to see this.


#67

I doesn’t work that way. While I may opt-out from a feature, other people will use it, so in the end I’ll end up using it as well: I can decide not to write in that style, but I’ll need to be able to read and understand it. And it’s not only about my own codebase, I peek into the sources of various packages quite often.


#68

So if I understand you correctly, your concern with the new syntax is that it’s hard to distinguish maps and tuples, both for beginners and advanced users?


#69

My personal opinion is that this feature makes the language more expressive, which I like, but it may also lead to code which is less direct/explicit and thus more difficult to read. Please note that I’m rather new and inexperienced with Elixir, so my fleeting opinions should be taken lightly.


#70
def index(conn, stuff) do
  stuff.account_id
  ...
end

Or maybe ..., stuff = %{account_id: account_id}, ... and stuff.filters etcetera.

I think there’s plenty of syntactic sugar already, and the proposal smells horribly confusing to me just to save some keystrokes. Often, such function headers are a refactoring smell to begin with, which is why I actually prefer the extra typing work - makes me think about what I’m doing :wink:


#71

A real life example I’m working on now in a graphql resolver:

this:

  def update_item(_, %{id: id, description: description, tags: tags}, %{context: %{actor: actor}}) do
    Cogo.Content.Items.update(id, %{description: description, tags: tags}, actor)
  end

Could be transformed into this:

  def update_item(_, %{id, description, tags}, %{context: %{actor}}) do
    Cogo.Content.Items.update(id, %{description, tags}, actor)
  end

Pretty attractive right? Especially for these kind of pass-through functions.


#72

In this particular situation, I believe he needs all those things in the pattern match because he’s checking for presence of the keys, which he has to do in the function head.


#73

It’s probably a matter of style, but that smells like validation which I’d do with a validate_stuff(...) call in the body; pattern matches are usually reserved for conditional syntactic sugar, I hardly ever match more than one or two things.


#74

I can only speak for myself. For me, this proposal makes it harder to distinguish maps from tuples, but most importantly has too many quirks around atom vs string keys. On the other hand, if we talk about your example (atom keys), then it does sound attractive, but I’d like to first consider other options (structs? direct map access?).


#75

I agree, I think this should just apply to atom keys. IMO it’s not worth having extra syntax to destructure string keys.
If it applies to maps, it should just work for structs as well, as they are just maps.

def get_name(%User{name}), do: name

#76

José said on the mailing list:

We don’t need to support both atoms and strings but whatever proposal we accept need to identify visually that we are matching on atoms/keywords. From my perspective, this is a non-negotiable requirement. If we can also support strings, that would be a plus.

Unfortunately, the syntax your talking about has already been rejected by core. So, you’d have to define some novel syntax that meets the above criteria.


#77

Sounds like Anecdata. I will share my own. I write little to no JS In my day to day. I did not realize this was possible in JS until these threads started popping up. I’m not sure if anyone has the numbers on how many JS devs came to Elixir. Basically, I do not think it is fair to say a huge portion of people know this.

I posted about OCaml having this in the mailing list (along with some other languages). I also pointed out OCaml has it work on their records (somewhat similar to Elixir’s structs, the point being that it has set keys) and not just maps in general. There are differences that are being overlooked here.

My worry about supporting one of atoms or strings, it will confuse new people to the language that do not understand the difference. I answered a question last night on StackOverflow about the difference between %{"key" => 1} and %{key: 1}, which means this will be an actual barrier for some people.


#78

Well, of course not everyone has experience with javascript but it’s a very big and popular platform which is being used everywhere (frontend/backend/native), and a lot of folks have a javascript frontend to their elixir backend.

With OCaml’s typesystem this is a bit of a different situation I believe because you have less data structures that don’t have a defined structure. Destructuring is also available in other languages on maps/dictionaries/objects, I think it would be too bad if such a useful feature should only be available for structs as a special kind of map. I also don’t see the reason why it would only be useful for structs and not maps. (In my graphql example it also handy in destructuring the graphql parameters).

Well we already have that in the language and it is too bad. Having punning for atom keys makes it more in line with the colon sugar that also only applies to atoms.

Honestly I think atoms should be an optimization by the compiler and not be available in user land, but that is not something easily changed for a language on top of the BEAM.

But for the general use case of a map as a datastructure without unpredictable user input, usually atom keys are the way to go. Having the ability to have any other type as key is a much smaller use-case, IMO, so it makes sense to improve the developer experience for that case.


#79

I would say the reasoning of Jose here inconsistent because with the colon sugar in maps it there is also no visual indication that it is about atoms instead of strings. I don’t think %{name} is much more unclear than %{name}a. In both cases you have to know/ look up what is going on.

Anyway, just trying to get this into the right direction based on three years of real world usage of Elixir (one of the first companies to adopt Elixir). I know Jose has the final word, and he’ll probably go for a different proposal, I just think it’s better to go for simplicity here. And I really think this would make our codebase way more cleaner/readable.

No offence though. I really like Elixir and Jose really does a great job to nurture the language, and I almost always agree with his decisions. I just wanted to give my honest opinion here :slight_smile:


#80

I don’t really understand this point. You can pass around maps, lists, whatever in OCaml just as easy as you can with Elixir. Admittedly it doesn’t make sense to because you want to limit the operations you can do on a piece of data. We also have structs and maps that can get passed around as easily as OCaml’s records. Maybe we shouldn’t be passing maps around so we can limit the operations we can perform on a piece of data? I know this won’t truly be type checked, but the point still stands.

This thread is not about adding destructuring to Elixir (which it already has), it is about adding some sort of short hand. Can you please point to other languages that are not JS that have this short destructuring for generic datatypes? It may influence the conversation.


#81

I would argue that in the real world you are dealing with untyped maps (not structs) in a lot of cases in Elixir even though the structure might be fixed. See for instance absinthe/graphql datastructures. There is nothing wrong with that in a dynamic language like Elixir. It adds to the flexibility and development speed not to type everything… trade-offs… I don’t think the discussion that most maps should actually be structs has influence on whether field punning is a useful feature.

You are right, I was not very precise, but of course I meant a shorthand that makes destructuring maps easier :). I think JavaScript by itself is a good example of where it became a very useful language feature. It’s one of the largest mainstream languages. Probably there are other languages, happy to know, not sure how it adds to this discussion.


#82

If there are other languages that have to deal with the multiple datatypes for keys (limited to string and atom for the sake of this discussion, because I don’t think you can really support a datetime key with any of the propsed syntax. Yes I actually use that for an application), we can look at how they accomplished this. If there is a solution that someone here has not come up with, it could sway people.


#83

Except a different head should be executed when filters is not defined. That’s the issue, the old API I’m replacing has a lot of calls that do wildly different things depending on passed in arguments. >.>

Precisely, otherwise I start getting massive branching trees of cases or other things, which ends up being even harder to read and follow…

It is for conditionals, not validation.

As I recall in OCaml it works on lists, tuples, records, objects, modules, and I thought there was something else…

Exactly, a typing system significantly changes things, this is a big part of why I find Elixir so verbose, it’s lack of a typing system. You can’t destructure on a map in OCaml for example, but a record in OCaml is like a struct in Elixir and an object in OCaml is more similar to maps in Elixir (row-typing).

Oh definitely not, atoms are way useful! I even wrote atoms in C++ a long time ago (first via a macro processing horror than via constexpr’s for C++11, this is the C++11 version) that optimize down to integers at compile-time (can even do something like "UPDATE"_atom64 as a case in a switch~). :slight_smile:

They are like a global enumeration, like Polymorphic Variants in OCaml (except they can’t hold data too like Polymorphic Variants but you can emulate’ish that via tagged tuples). :slight_smile:

Even C++ has destructuring and full-on matching libraries with such shortcuts too! ^.^

C++ can. ^.^

As an aside, I wish the defguard implementation added to Elixir was my more powerful version instead, then I could shorten my function heads substantially while being more readable in an easy way. Though with OTP 21 (not using in prod yet) I can do ‘most’ of that now… :slight_smile:


#84

If you want to move this discussion forward the correct place to do it is on the mailing list. That being said the core team has already made it clear that %{name} isn’t going to be accepted. So you’ll need to develop a pretty comprehensive argument or alternative. If this is a feature that you want now you might check out short maps or one of the many alternatives.


#85

This would work for me, but I guess it might be difficult to implement:

%{name:, description:}

It’s obviously a map, it obviously has atom keys, just the names are missing (because they are the same as the keys).