Come on Elixir, if PHP can do it, anybody can - trailing comma now allowed in function & method calls

Trailing commas without parenthesis will always be interpreted in the wrong way because we have no way of knowing what the last comma is. It is impossible to implement. We should also strive to reduce the amount of ambiguity in the language, introducing more feels like going in the wrong direction.

5 Likes

Oooo, I did not know it had this inconsistencyā€¦ I may need to start adding an ending optional ignored argument to all my multi-line (mostly macro) functions so I can keep , at the end. :slight_smile:

The language has a ton of ā€˜surprisingā€™ cases like this already, this would actually remove one of the surprises if trailing commas were allowed inside parenthesis. :wink:

4 Likes

This is exactly why we canā€™t have trailing commas. You are doing a function call without parenthesis so with trailing commas itā€™s ambiguous what the last argument is.

I hadnā€™t considered that.

However, it seems that the root cause of the ambiguity is that parentheses are optional. Thereā€™s nothing ambiguous about this, is there?

# syntax error
ArgsDemo.normal_args(
  10,
  "blue",
)

If thereā€™s a closing parenthesis, couldnā€™t the parser notice that and ignore the trailing comma?

We should also strive to reduce the amount of ambiguity in the language

I totally agree. Making parentheses required for function calls would remove the ambiguity, at some (arguable) aesthetic cost.

2 Likes

Indeed, Iā€™d have the parser only accept trailing commas if it is inside parenthesis, otherwise itā€™d be disallowed.

1 Like

Indeed, Iā€™d have the parser only accept trailing commas if it is inside parenthesis, otherwise itā€™d be disallowed.

Yes - or inside [].

1 Like

Yes, thatā€™s what we do inside brackets. But we donā€™t want to add it for function calls because, like I said earlier, we canā€™t support it for parenthesis-less functions and we feel it would be inconsistent and confusing to only support it for some function calls.

That would be a breaking change and would not possible until Elixir 2.0.

Iā€™m not sure this topic is worth discussing any longer. I think everyone feels like the loss of the trailing comma is worth losing over losing optional parenthesis. I remember the backlash when elixir started warning about omitting parenthesis when piping and Iā€™m fairly positive thats not something the community would be willing to trade.

I wonder why you would want trailing commas in function calls.

Functions in Elixir and Erlang are defined by their name and arity. A trailing comma in a function call seems to introduce unnecessary ambiguity when it comes to the arity of the called function.

Does foo(bar,) refer to foo/1 or foo/2 with the second parameter accidentally omitted?

Multi-line, especially useful for macroā€™s:

someMacro(
  ModuleName,
  a_very_long_argument_maybe_some_AST_for_the_macro,
  or_maybe - a_set_of + math_that / needs_to * be_performed,
  a: 1, # Or keyword list for those that like to use them without `[]`
  b: 2,
  c: 3,
)

Convoluted example, but it gets the point across. Plus it unifies the trailing comma syntax with [] and {} and %{} too, thus making things more consistent. :slight_smile:

Iā€™d never recommend a trailing comma in a single-liner, not in any of those.

But in this example, youā€™re still using the trailing comma for a keyword list and not for function parameters.

Each of those lines were intended to be exampled stand-alone. ^.^

Iā€™ve always felt conflicted about the syntactic sugar that allows dropping brackets on keyword lists if theyā€™re the final parameter of a function call. For instance, try explaining to a newcomer to Elixir why this should happen:

IO.inspect("bar", label: "foo")
# foo: "bar"
# "bar"
IO.inspect(a: "bar", label: "foo")
# [a: "bar", label: "foo"]
# [a: "bar", label: "foo"]

This bit of sugar makes reading code more confusing for a somewhat cool (but not essential, as far as I know), feature. If it were up to me, I would enforce brackets around keyword lists (and continue to push replacing keyword lists with map). So the previous calls would unambiguously be:

IO.inspect("bar", [label: "foo"])
IO.inspect([a: "bar", label: "foo"])

In any case, I lean more towards the side of keeping the syntax between function calls with and without parens as close as possible, and donā€™t think trailing commas inside function calls with parens moves the language in a good direction.

5 Likes

Absolutely thus. If Elixir did not start before maps were added to the BEAM, I bet that is what would have been done.

For this special case you can simply put the comma at the beginning of the line.
Elm is doing it this way and it feels not that bad to me.

%{
    one: "hello"
  , two: "goodbye"
+ , three: "we meet again"
}
2 Likes

I prefer the OCaml way. ^.^

  %{
    one: "hello",
    two: "goodbye",
+   three: "we meet again",
  }

I.E. Trailing commaā€™s. ^.^
(Although technically OCaml uses ; instead of commaā€™s to separate list and record elements, and commaā€™s are used to separate tuple elements, and function arguments have no separators at all).

2 Likes

Counterexample:

 %{
+  zero: 0
+ ,one: "hello"
  ,two: "goodbye"
 }

Anyway, this discussion is pointless, as I said earlier, since current version of the elixir formatter removes all trailing commas anyway.

There are tests in elixir/lib/elixir/test/elixir/code_formatter/containers_test.exs at 7f8136915fe249efa47a21a89ff0f04e880264fc Ā· elixir-lang/elixir Ā· GitHub that show that the removal of trailing comma is as expected.

Also I remember a discussion on core or the issue tracker where @josevalim said, that trailing commans will be removed by the formatter and that this is not an option to discus.

So as much as Iā€™d like to have them, in my opinion we can remove them from the parser in general, since the standard formatter, which we are all encouraged to use from 1.6s release on, will remove them anyway!

2 Likes

Technically Elmā€™s style is more like:

%{one: "hello"
 ,two: "goodbye"
}

As weird as that is, which would make your counterexample even more funky:

+ %{zero: 0
+  ,one: "hello"
   ,two: "goodbye"
  }

^.^;

1 Like

Wonderful idea!
Letā€™s destroy all structure in code and make everything look like everything else!
clap

1 Like

Structure is not syntax, the structure would remain identical here.

Iā€™m happy to drop this issue - itā€™s not the way I prefer, but itā€™s not important.