Proposal: Add field puns/map shorthand to Elixir

proposal
on-topic-only-please

#22

No that I’ve worked with many Elixir projects to check thoise case/pain points but I agree with you @jswny.

One may even say that “having 4 librarys that address this is the proof that we need it”… but, I don’t think it is. I’m good with the current syntax and formatter doing its job with the long maps.


#23

Thanks!

If this is a sticking point with people, I don’t mind dropping it. In previous discussions people seemed to prefer this and it does seem consistent with some of the other features in Elixir. However, this isn’t a sticking point for me.

Sigils are postfix as well. That’s where the inspiration came from.

All the rules are pretty much described in the first section. The rest of the sections mostly reiterate that the rules also apply in that particular situation or that field punning inherits the same behavior as key value pairs. My goal there was to show that things would be consistent throughout all the usages of the kernel special forms.

I agree. I already spent a couple days on this, so I figured I’d put it up unfinished.

As I stated in the proposal, I think the libraries have limitations that prevent them from being as useful or complete as they would be if they were part of the language.

That was my goal as well, but I’d be willing to concede to just structs.


#24

@blatyo you opened a discussion at elixir-core mailing lists, then moved it to the Has Map shorthand syntax in other languages caused you any problems? and now there is a third topic. The people opinions got spread and it’s not easy to follow the progress and parts of discussion are lost.

This thread seems to be more up to date and I want to quote few valuable contributions to discussion here

100 times this. Few keystrokes in exchange of learning a bunch of new, arguably friendly, semantics doesn’t seems to be a fine trade off for me.

%{point | x, y: 4}a #=> %{x: 3, y: 4}
%{point2 | x, "y" => 4}s #=> %{"x" => 3, "y" => 4}

%{point | y: 4, x}a #=> ** (SyntaxError) syntax error before: x
%{point2 | "y" => 4, x}s #=> ** (SyntaxError) syntax error before: x

^^^ That would be helpful imo, yes.

I don’t want to see an extra flag to denote data type in shorthand syntax - that’s contradiction by definition. I appreciate the effort, but that seems to be the line where I suggest to stop pushing this proposal, put it on the shelf and give idea more time to ripen.


#25

Great point! I guess some people forgot a history lesson that is called ActiveSupport::HashWithIndifferentAccess: if we will have a different syntax for different data types the next logical step will be to unify them and then bam - you have a 100 classed deep inheritance hierarchy :))


#26

I’m sorry. The first proposal on the mailing list was someone else’s that was rejected. The other topic I created on the forum wasn’t suppose to be about Elixir at all, hence it being in the other languages category. I linked to all those places when creating this one so that people could see them and I summarized the complaints.

I also don’t care about key strokes. My goal is to improve readability. A side effect of having more key strokes though, is that it is more information to parse. To me, this feature reduces the redundant information to parse and thus makes it easier to read. In some situations the reductions in characters also improves the readability by not wrapping statements to multiple lines, for example, separating where a value was extracted from that is used in a guard.

I understand why people may disagree with that and think this syntax will be less readable.

  1. There’s the argument from the user not being aware of the feature. In that situation, nothing the user is unfamiliar with is readable. To someone new to Elixir, most of language is unreadable. The best way to make this feature understandable in this situation is to try it and it would be easy to run something like this in IEx.
  2. There’s the argument that even someone familiar with the feature would find it less readable. Because of how simple the conversion is from foo to foo: foo is, that is less compelling of an argument to me. I think the presence of the modifiers does help to bolster this argument though, because you must first check the modifier and then read the map to understand it. If I could think of a way to do this for only atom keys, but have it be clear the feature was for only atom keys, I would go that direction. Then, I think this argument wouldn’t be that good.

I think the best way to improve an idea is for others to provide feedback. So, I disagree with your suggested approach.

If that were the next logical step, this proposal wouldn’t try so hard to distinguish between them.


#27

The only thing that I can come up with is this: use prefix modifiers instead of postfix.

I don’t like this:

%{x, y}a #=> %{x: 1, y: 2}

It’s like you read a very long sentence and the last word changes the whole meaning.

In general, I said “no” because:

  • the proposal introduces too many edge cases/quirks
  • it’s similar to the ~w sigil which is my pet peeve:
    • why have two ways to write the same thing?
    • it saves some typing, not worth it IMO
    • it always takes extra time for me to parse it
    • search-hostile which is an issue in a dynamically-typed language
  • {foo, bar} vs %{foo, bar} are way too similar

#28

This is the issue though. This information isn’t redundant. Elixir differentiates between strings and atom keys in maps. That fact is why you need this special syntax and is really the root of all my issues with this proposal. Adding a specific letter at the end of a map doesn’t make the code more readable from the perspective of conveying intent and information to the reader. It only obscures potential issues. Some folks have made the argument that a solution for potentially surprising behaviour would be to have better error messages. This solution is a tacit acknowledgement that we haven’t conveyed the proper amount of information in the code itself.

I think you’re conflating 2 different users here. The person you’re describing is someone who has picked up elixir as their first programming language. In which case I agree with your premise. Nothing they see is going to be readable until they can hold the semantics of programming in their head. But there is a 2nd person who is very familiar with programming and is looking at elixir for the first time. In this case I would argue that most of elixir is readable and depending on background you really only have to learn 1 or 2 specific syntax/semantic concepts. The most relevant to this conversation is pattern matching. This syntax adds a new wrinkle to pattern matching and introduces specific edge cases that the user then needs to remember.

I’d argue that the sigil modifiers are another source of pain. Personally, I see them very rarely used in production code. I don’t think this should be used as a justification for additional modifiers.


#29

The problem with this would be that it would conflict with the syntax for structs.

%a{foo}

# looks a lot like

%A{foo}

Even though, technically you can’t have a lower case struct that’s what it looks like.

I understand that sentiment. How do you feel about the struct only proposal described in the polls section? I think it addresses most of your concerns.


#30

I mentioned an alternate proposal to support structs only in the polls section above. Because there would be no modifiers and structs only support atom keys, it would be redundant information by this definition. Would those modifications change your opposition?

I was more trying to argue the general case that any language feature someone is unfamiliar with is a hinderance to them. I mentioned the most extreme case, because using the argument that people don’t understand something could be used to an extreme to remove almost any feature from a language. I agree there’s some middle ground, but saying someone might not understand this isn’t a sufficient argument against a feature.

This is a fair argument. I’ve only used them for regular expressions. The structs proposal in the polls section removes the need for modifiers.


#31

I’ve added 2 polls to the bottom of the proposal for these two follow ups.

How would people feel about a structs only approach?

This would mean maps are not supported and there are no modifiers at all. Additionally, only the %MyStruct{struct | field_pun} update syntax would allow puns. The %{struct | field_pun} syntax would not work, you would have to use key value pairs. I’ve specified two flavors. One where %MyStruct{struct | field_pun} is supported and one where it is not. See poll above.

I will assume that people who have voted that they don’t support this feature are continuing to not support it.

The second question is about requiring field puns to be first. Some people didn’t like that and I want to get an impression of which side people prefer.


#32

At least for me it’s not a typing speed thing but a readability thing, seeing this:

def index(conn, %{:account_id, filters, list, action, etc}) do

Looks far better than:

def index(conn, %{:account_id => account_id, "filters" => filters, "list" => list, "action" => action, "etc" => etc}) do

Which due to length often gets reformatted to this ugly horror of (yes this is actually output from the formatter):

def index(conn, %{
      :account_id => account_id,
      "filters" => filters,
      "list" => list,
      "action" => action,
      "etc" => etc
    }) do

/me still hates the no trailing commas the formatter does on multi-lines too, annoying inconsistency…


#33
iex(1)> defmodule :a do
...(1)> defstruct test: nil
...(1)> end
{:module, :a,
 <<70, 79, 82, 49, 0, 0, 5, 96, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 171,
   0, 0, 0, 18, 1, 97, 8, 95, 95, 105, 110, 102, 111, 95, 95, 9, 102, 117, 110,
   99, 116, 105, 111, 110, 115, 3, 109, ...>>, %:a{test: nil}}
iex(2)> %:a{}
%:a{test: nil}
iex(3)> 

#34

The first few times I would look at something like this, I would get really confused about where the account_id variable comes from.

@radarjones %:a{...} is different from %a{...}. You can have a struct with any atom name, but you cannot have a struct that is a bare word that starts with a lowercase letter.


#35

No, because it still introduces inconsistency into the language. I feel like I’m making the same points over again. The burden of proof is on you to demonstrate that problem you’re trying to solve outweighs any inconsistencies introduced to the language. I’m personally not convinced that this is even a problem that needs to be solved and based on the poll numbers (which are definitely not statistically valid but here we go anyway) it seems like I’m not alone :smile:.

This reason (and many others) is why I’m still livin’ that “skip the formatter” lifestyle.


#36

As I’ve stated, not fully happy with the syntax, I’d be fine with the string keys having some marker as well, but it is just an example of the readability in comparison to the long version. :slight_smile:


#37

True. I still think it just shows that all this prefix stuff in the name of short maps, could just add more confusion to the language for little benefit.


#38

I agree. Maybe I am wrong in this, but it seems that most people want this feature, or something like it, in order to shorten controller heads. At least that is the scenario that I see repeated most often. If that is the case, then I think the current packages that support this kind of thing are a great fit.

If that is not the case and you are trying to do something a lot more complex that those packages do not, or cannot, support, than I think it would benefit you, your team and your code to be more verbose and explicit in what you are doing so that you do not have to spend an extraneous amount of time trying to understand the code a couple weeks/months/years down the road when you now need to try to make modifications to it.


#39

I object to the inherent selection bias of these polls. By excluding options for saying, “No, we don’t want these either” it skews the results and the perception of how we should proceed with this proposal.


#40

Why are you matching all of this on the function head though? I typically match on the function head only what is needed for selecting between clauses. So I would write (for example):

def index(conn, %{account_id: account_id} = params) do
  %{
    "filters" => filters,
    "list" => list,
    "action" => action,
    "etc" => etc
  } = params

I don’t think there is any variation of code formatting of the original snippet that would be generally acceptable.


#41

Because I use different function heads for parts that are missing. :slight_smile:

I’m trying to fullfill an old API so I can’t really design what I want for the inputs so I have to handle very different functionality based on whether some query arguments exist or not.

As an aside, I wish route arguments were passed by atom (since they look like atoms in the path anyway), as then it would be easier to distinguish between a route name and some unknown passed in parameter (I just parse the param list though).

/me is really not a fan of ancient systems, but at least I’m slowly replacing it…