[Proposal] Shorthand syntax for keywords and maps

176 views
Skip to first unread message

Danila Poyarkov

unread,
Dec 20, 2025, 8:58:51 PM12/20/25
to elixir-lang-core
Hi everyone,

José Valim suggested I move the discussion here from my PR: https://github.com/elixir-lang/elixir/pull/15023

I've implemented shorthand syntax for atom-keyed maps and keywords:

```elixir
%{user:, conn:}  # => %{user: user, conn: conn}
[foo:, bar:]     # => [foo: foo, bar: bar]
f(name:, age:)   # => f(name: name, age: age)
%{map | a:, b:}  # => %{map | a: a, b: b}
```

I know this topic has been discussed many times before:

- Proposal: Short Hand Property Names (2017): https://groups.google.com/g/elixir-lang-core/c/XxnrGgZsyVc
- Consider supporting a map shorthand syntax (2018): https://groups.google.com/g/elixir-lang-core/c/NoUo2gqQR3I
- ES6-ish property value shorthands for maps? (2016): https://elixirforum.com/t/es6-ish-property-value-shorthands-for-maps/1524
- Has Map shorthand syntax caused you any problems? (2018): https://elixirforum.com/t/has-map-shorthand-syntax-in-other-languages-caused-you-any-problems/15403

Most of these discussed the ES6-style `%{a, b}` syntax, which José made clear had "zero chance" of being accepted — mainly because `%{a, b}` vs `{a, b}` differs by one character, making maps and tuples too easy to confuse.

The colon-based syntax `%{a:, b:}` is different. The `:` that signals "this is a key-value pair" stays there. There's no visual confusion with tuples because `{a:, b:}` is not valid Elixir syntax anyway.

José mentioned in the PR that he actually prefers this approach over bare variables, but it was "deemed not acceptable by most people" in a previous discussion. I'd like to understand what the objections were.

Reading through the old threads, I found these concerns:

- "Removing explicitness for the sake of brevity doesn't appeal to me." (Chris Keathley)
- "Shorthand syntax makes that coupling even less obvious" — if you change a key, you need to find all functions that relied on that variable name. (Chris Keathley)
- "This will just add complexity to the language to save a few keystrokes for advanced users." (Matt Widmann)

These discussions happened in 2016-2018. Since then, Ruby 3.1 shipped this exact syntax in December 2021 — almost 4 years ago. The syntax is `{x:, y:}` for hashes and `foo(x:, y:)` for keyword arguments, exactly what I'm proposing for Elixir.

The Ruby reception was mixed at first — Bozhidar Batsov (RuboCop maintainer) was critical (https://batsov.com/articles/2022/01/20/bad-ruby-hash-value-omission/) but still allowed it in RuboCop defaults. Four years later, the syntax is widely used.

The same pattern (sometimes called "field punning") also exists in Rust and OCaml.

`%{user: user, conn: conn}` is already common in Elixir — this just removes the repetition. The colon stays visible, so it's not as "magic" as the bare variable approach. And Ruby has been using it for 4 years now without issues.

The implementation is ready and all tests pass. I'm curious whether opinions have changed since 2018.

Zach Daniel

unread,
Dec 21, 2025, 1:12:13 AM12/21/25
to elixir-l...@googlegroups.com
I'm in support of this 👌

It's a reasonable trade off from other concerns and as someone who works with people moving from other languages to Elixir often, they are *constantly* looking for this syntax. Given that this exact syntax is used in other languages also adds some regularity to it, despite my personal preference for js style %{a, b}. The "accidentally being a tuple" issue with that syntax goes away for 99% of cases conveniently with the type system FWIW :)


--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/elixir-lang-core/995a7fec-5992-484a-88c2-5aae3844f60fn%40googlegroups.com.
Message has been deleted
Message has been deleted

Cameron Duley

unread,
Dec 21, 2025, 4:35:39 PM12/21/25
to elixir-l...@googlegroups.com
Without passing my personal sentiment one way or the other, I’ll throw out these challenges:

Every piece of syntax composes with every other, creating a manner of ‘cyclomatic complexity’ in syntax, for lack of a better term. Elixir already has lots of syntax, so the marginal cost of complexity increases with each bit introduced.

The current syntax sugar for atom keys is already reasonably expressive without compromising visual explicitness.

While the current syntax can be repetitive, it implicitly nudges people away from complex extractions in clauses by causing (subjectively) ugly line-wraps to occur earlier.

The "accidentally being a tuple" issue with that syntax goes away for 99% of cases conveniently with the type system FWIW :)

The corollary follows that gradual static typing also makes both bracket access and dot-access safer, which could obviate map destructuring for folks in some cases.




--
Thanks,

Cameron Duley

Ryan Winchester

unread,
Dec 21, 2025, 5:07:33 PM12/21/25
to elixir-lang-core
I wish for this often.

I would happily settle for this just to have it, although I don’t like the syntax and also prefer the %{a, b} syntax like other languages (JS/TS, Rust, ...)
Message has been deleted

Joseph Lozano

unread,
Dec 21, 2025, 8:15:41 PM12/21/25
to elixir-l...@googlegroups.com
Would this work for destructuring too? 

```elixir
%{foo:, bar:} = my_map # assigns `foo` and `bar`
```

Allen Madsen

unread,
Dec 21, 2025, 8:33:15 PM12/21/25
to elixir-l...@googlegroups.com

Данила Поярков

unread,
Dec 21, 2025, 10:42:09 PM12/21/25
to elixir-l...@googlegroups.com
Yes, you can try that on my PR:

bin/elixir -e '%{foo:, bar:} = %{foo: 1, bar: 2}; IO.inspect({foo, bar})'

You received this message because you are subscribed to a topic in the Google Groups "elixir-lang-core" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elixir-lang-core/qyB5diWvJh8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/elixir-lang-core/8f903968-880c-44d2-8d4c-4c5a20be3c61%40app.fastmail.com.

Allen Madsen

unread,
Dec 22, 2025, 12:15:49 AM12/22/25
to elixir-l...@googlegroups.com
It'd be nice to support pinning as well.

x = 1
%{^x:} = %{x: 2} #=> %{x: ^x} = %{x: 2}



Brandon Gillespie

unread,
Dec 22, 2025, 10:52:01 AM12/22/25
to elixir-l...@googlegroups.com

Please do not do this WITH THIS SYNTAX (but I really do want destructuring/etc).

It looks like an error, no matter how hard you squint, nor rationalize.

There is no reason to repeat Ruby's mistakes, nor other languages doing the same. "because they are doing it" is not a reason.

The other problem with this is it is optimizing for the /advanced/ user, and not the common and new user. The community should be focused on making it EASIER to get into elixir, not harder.

New users coming from the biggest languages out there are what we should consider, not those with less popular languages. And like it or not, the popular languages are Java, Python, Javascript. None of them support the proposed visually borked syntax.

if anything, of those top three languages, Javascript does it with bare variables—so the only argument with weight (imho) that "other languages do it" would be for bare vars. But José has declined that syntax (I forget the reasons).

If the option for bare vars is off the table completely and forever, perhaps consider another token?

Asterisk almost could work. In spirit it almost hearkens back to C's pointer. And in this case used as a unary operation it wouldn't collide with multiplication, which is a binary operation.

I don't love it, but fwiw:

```
asdf = "foo"
%{*asdf}
```
=> `%{asdf: "foo"}`

```
%{*foo, *bar} = %{foo: "narf", bar: "boop"}
IO.inspect({foo,bar})
```

=> {"narf", "boop"}

But just in my own opinion, anything extending the core syntax should always keep "new programmers" as a key metric for if it'll work well.

-Brandon Gillespie
Message has been deleted

Allen Madsen

unread,
Dec 22, 2025, 12:08:13 PM12/22/25
to elixir-l...@googlegroups.com
Another language doing something certainly isn't a reason to adopt it. However, widespread usage of a feature in a language speaks to its usefulness and learnability. As stated previously, usage of this syntax is used pretty widely in the ruby community. So, I don't personally buy the "this is hard to learn" argument, because there's evidence to the contrary. 

It's also worth noting that Elixir would have the same reason as ruby to use the non-bare word syntax. In elixir you can do:

def foo(bar, baz: baz) do
end

Where the keyword arguments are gathered into a keyword list. Barewords here wouldn't make sense by themselves even if you wrap them in a list.

# not the same
def foo(bar, baz) do
end

# is this matching [baz] or [baz: baz]
def foo(bar, [baz]) do
end

The colon doesn't have that problem.

def foo(bar, :baz) do
end

Towards the recommendation for using *, I think that is a less good option because it looks like the splat operator in ruby and the pointer operator in other languages.



Brandon Gillespie

unread,
Dec 22, 2025, 12:41:43 PM12/22/25
to elixir-l...@googlegroups.com

I'm trying to avoid being too argumentative here, but leaning back on "its used in ruby so it can't be hard to learn" just doesn't correlate. That it is in ruby doesn't implicitly make it easy to learn. As I said: we don't have to repeat another language's mistakes 😂

FWIW, I have never liked ruby. 😂 I don't code in it, despite trying a few times. I don't think of Elixir as still a derivative of Ruby, and I'd suggest decoupling that notion, TBH. We should focus on Elixir, as itself. not as a derivative of something else. And I think we should focus on expanding the dev pool /outside/ of ruby, not limiting within it.

As I hire new devs and train them into things, they come knowing other popular languages like python and javascript. I generally don't target ruby devs. And they're as rare as Elixir devs, TBH.

I hadn't considered a spread assignment (or whatever you want to call it) as it would apply to keywords. Me personally? I'd only ever use it with maps, so I'd be 100% fine if it was simply limited to maps. Keywords already have a lot of differences from maps anyway. On that assertion, I'm not sure what other objects there are to using it only with maps.

Splat operator: Sure. It was just an off-the-cuff suggestion. Saying there could be something else. Fixation on something that's a broken syntax in any of the top3 languages just makes it harder and more eldritch, also raising the bar for new devs.

All the arguments I've heard for the colon syntax center around "Ruby & a few others do it this way" (and IMHO because somebody else does it isn't ever a good reason), and "I want it, so is this a good enough concession?"

If this type of feature is really needed, I'd suggest even a working group session of interested parties, and just wipe the slate clean. Star by clearly defining the core desire/need, then talk through all the various challenges, throw out 5~10 more options, discuss, etc. (I don't know if this is already a thing the community does, or not).

But to me this isn't something that should be done via a PR and an email conversation with a few people who happened to be noticing things on the list during a holiday season.

Just my "two cents" as it were, from one normally watching in the peanut gallery.

-Brandon Gillespie

Данила Поярков

unread,
Dec 22, 2025, 1:26:20 PM12/22/25
to elixir-l...@googlegroups.com
I understand the concerns about adding syntax and about Elixir having its own identity. I’m not arguing that Elixir should copy Ruby, or that Ruby’s choices are automatically good for Elixir.

That said, it’s also true that a significant portion of Elixir users historically come from the Ruby ecosystem. Elixir has often been described (rightly or wrongly) as having a Ruby-like surface syntax with Erlang semantics, and that has been part of its approachability for many people — myself included.

In that context, the Ruby comparison isn’t about imitation, but about shared expectations around readability and ergonomics. Ruby 3.1’s {a:, b:} is not a novelty feature; it’s a conservative form of value omission inside already-keyed syntax. The proposal here does the same thing: it removes duplication from %{a: a} without introducing new semantics or implicit behavior.

Importantly, this syntax is purely optional. Existing explicit forms remain valid and idiomatic. I intentionally did not enforce it via the formatter, precisely to avoid pushing it onto users who don’t want it or don’t find it clearer.

From a learning perspective, this doesn’t raise the floor: users already must learn atom keys and keyword syntax. This only shortens an expression they already understand, and only when the variable name already matches the key.

I fully agree that clarity should trump cleverness. My argument is that %{a:, b:} stays within Elixir’s existing visual and semantic rules, while addressing a very common repetition pattern that many users encounter in real codebases.

Christopher Keele

unread,
Dec 22, 2025, 4:35:34 PM12/22/25
to elixir-lang-core
As the driving author of the last major discussion about this on this mailing list (which ultimately resulted in implementing this macro), I learned there are 5 core things any proposal for field punning must either address or dismiss to avoid re-treading ground:

1. How to handle atom and string keys
2. How to support maps and keyword lists
3. How to support the variable pinning operator
4. How to ensure visual clarity from other forms (ex, confusion with a bare-words tuple {foo, bar} vs %{foo, bar})

Note that there are few extensible compromises here, as not including one feature in the above tends to lead to a syntax that eliminates its possibility in the future.

Since that conversation, I'd argue only 4. is any clearer since our last go at this: as Zach points out, the type system can assist in catching typos here now.

As a distant 5th concern, there is also the question of which syntax people prefer (personally for me, bare-words a la JS, which seems to have grown in popularity), but no syntax can be complete without navigating the above concerns.

My personal argument for a JS-style "barewords" identifier over the Ruby-symbol-syntax is that since it looks like a normal variable, it plays visually well with support for the variable pinning operator if that is added to the feature matrix.

Michael Neumann

unread,
Dec 23, 2025, 4:03:13 AM12/23/25
to elixir-l...@googlegroups.com


Brandon Gillespie <bra...@cold.org> schrieb am Mo., 22. Dez. 2025, 16:52:

Please do not do this WITH THIS SYNTAX (but I really do want destructuring/etc).

It looks like an error, no matter how hard you squint, nor rationalize.


That's a good point. The human expects that colon ":" is followed by something. 

There is no reason to repeat Ruby's mistakes, nor other languages doing the same. "because they are doing it" is not a reason.

In Ruby, it's more orthogonal. You already have it in the way keyword arguments are declared:

def my_fun(a:, b:)

So adding {a:} to mean {a: a} is actually fine for Ruby. When you see "a:" in Ruby, this could already mean that "a" is both a Symbol and a local variable.

The other problem with this is it is optimizing for the /advanced/ user, and not the common and new user. The community should be focused on making it EASIER to get into elixir, not harder.

New users coming from the biggest languages out there are what we should consider, not those with less popular languages. And like it or not, the popular languages are Java, Python, Javascript. None of them support the proposed visually borked syntax.

if anything, of those top three languages, Javascript does it with bare variables—so the only argument with weight (imho) that "other languages do it" would be for bare vars. But José has declined that syntax (I forget the reasons).

If the option for bare vars is off the table completely and forever, perhaps consider another token?

Asterisk almost could work. In spirit it almost hearkens back to C's pointer. And in this case used as a unary operation it wouldn't collide with multiplication, which is a binary operation.

I don't love it, but fwiw:

```
asdf = "foo"
%{*asdf}
```
=> `%{asdf: "foo"}`

I like this in general. But, if asdf is a Map itself, I'd expect that it to be flattened. The "*" already has a meaning when coming from Ruby, so be careful with that.


Actually, I'd rather do this:

```
%{asdf: ^}
```

which means, put in the variable of the same name as the key. Replace the "^" with whatever other sigil you want.

Just my 5 cents...


benjamin...@gmail.com

unread,
Dec 23, 2025, 5:19:35 AM12/23/25
to elixir-lang-core
Why can’t this be done with a sigil, which also has precedence for shorthands like this. Elixir even states that it is build to be extendable without the need for syntax changes. Personal I dislike prior art justifications for shorthands and syntax changes like this, as it can potentially block or limit syntax changes in the future for new data structures. I think we would all be better off with a sigil, that way we also don’t limit ourselves to atom keys.

Andrew Timberlake

unread,
Dec 23, 2025, 5:34:34 AM12/23/25
to elixir-lang-core
It can be done with sigils (see https://github.com/whatyouhide/short_maps; related blog post on retiring the library: https://andrealeopardi.com/posts/a-story-of-regret-and-retiring-a-library-from-hex/)

Another option is macros, which I implemented in https://github.com/andrewtimberlake/shorthand; and related  blog post: https://andrewtimberlake.com/blog/2024/11/shorthand-maps-for-elixir

To add a vote: I’m not in favour of the %{foo:, bar:} syntax. I would prefer to see %{foo, bar}. I’m quite happy with the macro form in Shorthand, which I use in every project.

Andrew

On December 23, 2025, "benjamin...@gmail.com" <benjamin...@gmail.com> wrote:
Why can’t this be done with a sigil, which also has precedence for shorthands like this. Elixir even states that it is build to be extendable without the need for syntax changes. Personal I dislike prior art justifications for shorthands and syntax changes like this, as it can potentially block or limit syntax changes in the future for new data structures. I think we would all be better off with a sigil, that way we also don’t limit ourselves to atom keys.


On Sunday, December 21, 2025 at 5:07:33 PM UTC-5 ry...@winchester.dev wrote:
I wish for this often.

I would happily settle for this just to have it, although I don’t like the syntax and also prefer the %{a, b} syntax like other languages (JS/TS, Rust, ...)

On Sunday, December 21, 2025 at 2:12:13 AM UTC-4 zachary....@gmail.com wrote:
I'm in support of this 👌

It's a reasonable trade off from other concerns and as someone who works with people moving from other languages to Elixir often, they are *constantly* looking for this syntax. Given that this exact syntax is used in other languages also adds some regularity to it, despite my personal preference for js style %{a, b}. The "accidentally being a tuple" issue with that syntax goes away for 99% of cases conveniently with the type system FWIW :)


Andrea Leopardi

unread,
Dec 23, 2025, 8:48:39 AM12/23/25
to elixir-l...@googlegroups.com
I’m going to chime in but not in the official capacity of a member of the core team—this is just my personal opinion.

Elixir is a very flexible and extensible language. In my experience with coworkers, OSS, and quite a few codebases, I find that folks tend to lean on simpler constructs whenever possible. My prime example of this is the ~w sigil; most of the time I see folks leaving comments on things like

~w[started completed errored]a

asking to rewrite it as 

[:started, :completed, :errored]

so that readers don't have to know or care about ~w. I find myself agreeing quite a lot. 

With a feature like this, I get that some folks coming from Ruby or JS can feel "at home" but it's going to be one more thing to have to think and reason about for everyone else. If you see:

def create(%{user:, conn:}) do
end

now you'll have to think through:

  1. This only matches if this is a map that has :user and :conn atom keys in it
  2. If it is, then this will introduce user and conn variables
For me that is too big of a cognitive burden for the reader (myself included).

Other things I found net negatives:
  • If you want to change the variable name, because say you want it to become user_with_preloaded_stuff, now you have to change syntax because the previous one doesn't work anymore.
  • We have very few (if any?) constructs that introduce variable names that are not "pattern matching" where the variable name is explicit. This breaks that expectation.
  • ^ is yet another cognitive thing to keep in mind.
The type system will let you do params.user and params.conn "just as safely", as others mentioned in the thread. 

Final thoughts: others have mentioned something along these lines, but to me a stable and mature language like Elixir should default to not adding stuff. To add features, and especially syntax, there should be such a strong case in favor of it that not adding it becomes the worst option. I don't think this is the case, by far.

Simon McConnell

unread,
Dec 24, 2025, 3:16:41 PM12/24/25
to elixir-lang-core
Can it be mixed with the normal type of pattern matching?, e.g. %{a:, b:, c: %{d:, e: e_data} = c, f:} = something.  If not, that's a hard no for me.  Otherwise, I would probably use it and am not strongly against it but I would err on the side of caution.  I get the appeal of shortcuts but I think I prefer to suffer typing it all out than to add more syntax.  

I'm training someone [in something other than Elixir] and I notice myself explaining all the caveats as I go.  It would be easier for us both if there weren't so many caveats to explain.  For me, this is one of those caveats.  I can imagine questions like: why can't I do %{"a" => , "b" => } = map or how does [a:, b:] = kw work with ordering?

FWIW, doesn't gleam use the a: syntax?  I prefer that to the bare variables or adding more complexity with an * or other symbol.  It seems more obviously a syntactical shortcut than bare variables.

I tend to do all the extraction as the first line in the function, where I want it to line wrap.  As opposed to in the function head, where I would rather it did not wrap and where I may be pattern matching to dispatch to specific function heads.  As far as I know, the dot syntax is slower than pattern matching on a map.  I would prefer that penalty was removed, which is not feasible IIRC, to adding this syntax.

IMO, the ~w[a b c]a syntax should be avoided because you won't find it when searching for :a and it doesn't line wrap.
Reply all
Reply to author
Forward
0 new messages