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


#1

What's new in Elixir - Dec/17
Upgraded to 1.6, strange error from mix compile
#2

lolwat.


#3

If that’s your biggest gripe about Elixir, then I think Elixir is doing a pretty great job


#4

They’re probably allowing this for the same reasons that Elixir doesn’t mind if you put a trailing comma at the end of a Map:

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

Adding an extra key to this map would lead to:

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

Where the diff would simply be:

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

If it didn’t allow for a trailing comma, then you would have to write the initial map as:

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

Then, to add another key, you would need to add the comma to the 2nd key-value pair and add the third line, which would result in a diff showing one line modified (two) and one line added three.

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

Looking at this diff initially, it is hard to know that two did, in fact, not change at all. It was only three that was added.

So I can see sense for allowing this sort of thing in PHP’s function calls… and to that extent, in Elixir’s calls too. Just to make diffs a little easier to mentally parse.

It feels like making Elixir do this wouldn’t be too hard a task, but I wouldn’t even know where to begin myself.

Thanks for sharing, @ryanwinchester.


#5

PHP allows a set of things I’m very glad Elixir doesn’t…

Wouldn’t this make Elixir’s syntax ambiguous? For example, how would you parse this:

f a, b, c,
d e

as

f(a, b, c, d e)

or

f(a, b, c,)
d(e)

Or would the trailing comma be allowed only if there were parenthesis?


#6

No trailing comma please. The PHP era is over. Go Elixir Go.


#7

I don’t get it… Correct me if I’m wrong but you already can do that in Elixir

# this is valid...
my_variable = div(
 10,
 2
)

#8

Seems more progressive to remove the need for commas altogether - “less is more”. Clojure (LISP) made me aware of that and even GraphQL picked up on the idea:

####2.15 Insignificant Commas

Comma ::
,
Similar to white space and line terminators, commas (,) are used to improve the legibility of source text and separate lexical tokens but are otherwise syntactically and semantically insignificant within GraphQL query documents.

Non‐significant comma characters ensure that the absence or presence of a comma does not meaningfully alter the interpreted syntax of the document, as this can be a common user‐error in other languages. It also allows for the stylistic use of either trailing commas or line‐terminators as list delimiters which are both often desired for legibility and maintainability of source code.


#9

@WolfDan, maybe You missed the trailing comma.

It is about doing this

my_variable = div(
 10,
 2,
)

#10

Yes, go lisp syntax instead. The simplest syntax of them all. Of course it is a little anti-elixir in that it enforces parentheses while elixir allows you to not use them. :wink:


#11

Oh now I get it ^^ thank you!

Hum it seems useless hahahaha


#12

I’d like a lisp that would compile into Elixir modules. It’s not very hard to do, only a little boring because of little details such as designing a sensible standard library, tooling, sourcemaps, etc. You would know, of course xD

Why Elixir and not Erlang?

First, being able to interoperate with Elixir macros is huge, and Elixir’s AST oddities (like do blocks and the internal __block__ node) map relatively well to lisp’s progn or the scheme equivalents. With one or two helper macros, one could use Phoenix as is. This interoperability alone makes ir worth it to compile to Elixir instead of Erlang. Phoenix’s “guts” very well designed (PubSub, Channels, Presence), although one might dislike some surface characteristics. And Phoenix is so easy to customize that it doesn’t matter anyway.

Phoenix was basically the only reason I went for Elixir instead of LFE. One can debate the timeless beauty of lisp until the cows come home, but Elixir has Phoenix and Phoenix has channels, and channels are shiny and modern and won me over xD

Second, compiling to Elixir means we can use elixir’s tooling: Mix as a build tool (with a custom compiler), ExDoc for documentation and the new BEAM chunks for documentation and better error reporting in pattern matches.

I’m not familiar with the Erlang equivalents (rebar and erldoc), but again, Mix makes it easy to integrate with Elixir libraries.

@OvermindDL1 has been working on a toy implementation, which nonetheless has read macro support.

This project on Github also implements a lisp interpreter on Elixir with static types and HM type inference: https://github.com/tpoulsen/terp


#13

Trailing comas would only be allowed with parenthesis.


#14

Hear hear! Full ML style would be really nice actually. ^.^

I’d already have that done if I had time, maybe during thanksgiving or christmas breaks…

Eh, it’d be easy enough for a lisp’y erlang to access elixir macro’s and transform them to it’s internal syntax, though that would be more work over just going to elixir to begin with. ^.^;

Ditto to be honest. If not for Phoenix and Hex I’d be using erlang and lfe (probably together) as I prefer both of their syntax’s (and lfe’s substantial features, though it still needs read-macro’s! ^.^) better.

Yeah read-macro support (even scoped!) is nicely trivial to do. ^.^

I’ve actually been played with an idea to do a Staged compilation pipeline of LISP, this is how macro systems work in most languages (not lisp’s) and although it is ‘slightly’ more limiting, it allows me to output (even typed) ‘final generated code’ much more easily.

This thing looks really cool, I need to play with it, it looks similar to what I was already creating. ^.^;

Yup. I’d like it in Elixir especially as I tend to do multi-line function calls in many cases (because macro’s, so I cannot just split them out into different bindings).

(Side comment, OCaml has got an Elixir-like Macro system via a PPX just recently ^.^)


#15

I found this thread because I was independently wishing for trailing comma support for arguments.

I have an Ecto query like this:

    from recipe in query,
      join: id_and_rank in matching_recipe_ids_and_ranks(ts_query_string),
      on: id_and_rank.id == recipe.id,
      order_by: [desc: id_and_rank.rank]

If I want to see what the results look like without the order_by:, I can’t just comment out that line; I have to also remove the trailing comma on the previous line.

Similarly, I can’t just add a second order_by: without adding a comma to the existing line.

I also would like for Elixir to be consistent in how it treats list vs non-list arguments with regard to trailing commas.

defmodule ArgsDemo do
  def list_args(args \\ []), do: IO.inspect args
  def normal_args(arg1, arg2), do: IO.inspect [arg1, arg2]
  end
end

# works fine, because lists can have trailing args
ArgsDemo.list_args(
  [
    {:size, 10},
    {:color, "blue"},
  ]
)

# works fine, because it's syntax sugar for a list
ArgsDemo.list_args(
  size: 10,
  color: "blue",
)

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

#16

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.

In the code below, why would the parser not think other_expression() is the last argument of the from function call?

from recipe in query,
  join: id_and_rank in matching_recipe_ids_and_ranks(ts_query_string),
  on: id_and_rank.id == recipe.id,
  order_by: [desc: id_and_rank.rank],
other_expression()

#17

Even though we were at a point where trailing commas were supported by the grammar, they will be removed by the formatter… At least this is for map and list literals. I see no reason why the formatter should behave differently on function arguments.

But since I’m forced to use them in go, I really got used to them (and that they do nmot create much noice on diffs)[1].

[1] go fmt has the bad habbit to align values in multiline struct literals, which creates a lot of diff-noise though…


#18

Do you mean “this is why we can’t have trailing commas in parenthesis-less calls”? I see no argument in your reply for disallowing them in parenthesis calls.


#19

Yes, the example used a non-parenthesis call so that’s what I commented on. We shouldn’t allow them with parenthesis either because that would be inconsistent and prone to mistakes because the assumption is that the same syntax is allowed for all calls regardless of parenthesis.


#20

In my mind allowing trailing parenthesis would be more consistent as

  1. trailing parenthesis are allowed in lists, tuples, and maps (in fact this inconsistency is why I would like trailing commas in function calls in the first place)

  2. trailing commas are allowed in bracketless keyword lists at the end of function calls:

foo(
  bar,
  baz: 1,
)

Furthermore, it is not clear to me why trailing commas must be explicitly disallowed in parenthesis-less function calls. Sure, they would never be useable because they would almost always be interpreted as they are now, but if that inconsistency—which already exists in other parts of the language—bothers you, striking parenthesis-y trailing commas is not the only solution. (Parenthesis are commonly used to resolve abiguity anyways, this should be nothing new.)

Lastly, when you say

are you speaking from experience? I ask because that being an issue is not at all obvious to me. As mentioned above, the assumption that the same syntax is allowed for all calls regardless of parenthasis does not hold: trailing commas are allowed at the end of bracketless keyword lists inside parenthasis calls, but not inside parenthasis-less.