Keyword inside tuple shorthand syntax {a: 1, b: 2}

This could resolve to {[a: 1, b: 2]}. Was it ever considered to allow such syntax? Notice this: {:abc, a: 1, b: 2} and this: my_fun(:abc, a: 1, b: 2) work as I described. As I understand, the compiler is able to figure out that the second part is a keyword list.

I can see that it is similar to %{a: 1, b: 2}, and it may be misleading for people coming from Ruby (hash syntax).

Context: I’m working on Hologram’s template engine. If the shorthand syntax worked for tuples as well, it would simplify a few things since Hologram uses tuples for interpolation and prop syntax, e.g.

<MyComponent my_prop={:my_value} />

or

<button $click={:do_something, a: 1, b: 2}>Click me</button>

or shorthand class attribute syntax:

<div class={button: true, "button-active": false}></div>

This issue can be handled by the template parser, but I feel that it may be beneficial to support it in the Elixir compiler.

1 Like

My supported-only-by-vibes take is that when a user writes {a: 1, b: 2} they are far more likely to be typoing either a map literal (missing %) or a kwlist literal (wrong kind of bracket). :man_shrugging:

5 Likes

I’m rather confused. Tuples do not support key/value pairs, so I don’t have a picture of what you want. Perhaps you want property lists, which are used more in Erlang than Elixir?

@gregvaughn Yes, it’s indeed confusing - the syntax, see:

iex> {:do_something, a: 1, b: 2}
{:do_something, [a: 1, b: 2]}
iex> {a: 1, b: 2}
** (SyntaxError) invalid syntax found on iex:2:1:
    error: unexpected keyword list inside tuple. Did you mean to write a map (using %{...}) or a list (using [...]) instead? Syntax error after: '{'
    │
  2 │ {a: 1, b: 2}
    │ ^
    │
    └─ iex:2:1
    (iex 1.17.0-rc.1) lib/iex/evaluator.ex:295: IEx.Evaluator.parse_eval_inspect/4
    (iex 1.17.0-rc.1) lib/iex/evaluator.ex:187: IEx.Evaluator.loop/1
    (iex 1.17.0-rc.1) lib/iex/evaluator.ex:32: IEx.Evaluator.init/5
    (stdlib 6.0) proc_lib.erl:329: :proc_lib.init_p_do_apply/3

How about something like this?
<div {:class, button: true, "button-active": false}></div>

1 Like

It’s more rather about consistency. As mentioned this is supported in other cases, so I think same i.e. it should be supported in tuples.

1 Like

I can get on board with the consistency argument but I think the desire for this would be realllllly niche. I’ve had the exact thought as @bartblast were I was thinking it would be nice shorthand for this to work in HEEx, but that is literally the only time. I think it’s more beneficial not introduce the ambiguity of %{a: :b} vs {a: :b} for a niche purpose.

1 Like

Looking at Syntax reference — Elixir v1.16.3 I’m wondering if the syntax sugar in tuples should rather be removed. The atom shorthand within […] and %{…} works the same, but is considered a feature available for both those types and is not available for tuples.

The syntax sugar for skipping square brackets is documented to apply for the last parameter of calls. Creating a tuple is not a call. The closest comparison to the creation of a tuple would be the creation of a binary <<…>>.

4 Likes

Oh, in that case I can partially agree. However we need to keep in mind that 2-element tuples are considered literals, see:

iex> quote do
...> {:a}
...> end
{:{}, [], [:a]}
iex> quote do
...> {:a, :b}
...> end
{:a, :b}
iex> quote do
...> {:a, :b, :c}
...> end
{:{}, [], [:a, :b, :c]}
iex> quote do
...> {:a, :b, :c, :d}
...> end
{:{}, [], [:a, :b, :c, :d]}

If we go this way we should discuss if it could be allowed only for 2-element tuples. However such behaviour may be found as confusing. I guess that Elixir core team decided to make it work tuples like that because there is no real need for a list as the only element of tuple - instead we could simply use a list. So as always we end up with same thing: such stuff are related only to template engines. If all above is correct I guess this behaviour would not be changed. :thinking:

I’m not sure this matters. Optional square brackets are a syntax feature, which doesn’t affect AST. At least outside of the optional metadata for the formatter, which might care.

iex(1)> quote do: call(:a, test: 1, test: 2)
{:call, [], [:a, [test: 1, test: 2]]}
iex(2)> quote do: call(:a, [test: 1, test: 2])
{:call, [], [:a, [test: 1, test: 2]]}
1 Like

My objection is that if

{a: 1, b: 2}

is the same as

{[a: 1, b: 2]}

then why not also

{[a: 1], [b: 2]}

The role of the comma becomes far too ambiguous.

P.S. I don’t even like the “invisible brackets” implicit in my_fun(arg_1, opt_a: 1, opt_b: 3). I prefer an excess of clarity to an excess of magic.

1 Like

Which, the a: :b syntax?

Looking at this sort of drives home too: outside of OP’s situation, why would you ever want to wrap a single keyword list in a tuple? One-element tuples aren’t encouraged in Elixir (and rightly so, AFAIC) and this syntax would push against that.

1 Like

Good idea, but I’d rather keep the attribute name out of the curly braces.

Even if this is not supported by the Elixir compiler, I’ll be able to support it through the template parser. The keyword list square brackets can be added automatically if special cases are detected, and the Elixir compiler will still be used to validate the final code since the template is a regular sigil macro. However, we won’t be able to say that everything inside the curly braces (or including the braces) is a valid Elixir code.

It’s my favourite Elixir syntax sugar, on the other hand… :man_shrugging:

1 Like

Why not document it instead of removing it, then? Besides, I doubt removing it is even possible without breaking backward compatibility.

I don’t think it’s that niche, especially since we often return tuples as function results, like this: {:ok, my_opt_1: 1, my_opt_2: 2}

Plus, I bet that half of the Elixir apps in existence use some form of a template engine, be it HEEX, Surface, or soon Hologram, and such sugar syntax would be beneficial in those cases.

It not being documented likely means it not being intended – and if so it would be a bug. Fixing bugs can break compatibility.

I’ve honestly never run into this myself, though I’m certainly not saying it doesn’t exist! But for example Ecto Multis use maps in their return tuples. I only see return keyword lists as a hinderance here since it makes pattern matching harder.

My reservation here though is just allowing a single {a: :a, b: :b} though I guess that doesn’t really hold too much water, especially since single element tuple are allowed in general.

1 Like