[proposal] Consider supporting a map shorthand syntax

1,019 views
Skip to first unread message

Devin Torres

unread,
May 31, 2015, 6:19:46 AM5/31/15
to elixir-l...@googlegroups.com
Taking a pretty cool page out of ES6:

# If `method`, `url`, `headers`, and `payload` are already bound
%Request{method, url, headers, payload}

# If e.g. `payload` hasn't been bound yet
%Request{method, url, headers, payload: get_payload()}

Aleksei Magusev

unread,
May 31, 2015, 2:29:46 PM5/31/15
to elixir-l...@googlegroups.com, de...@devintorr.es
It would be awesome to have that syntax; and for update as well.
I assume that Alexei Sholik also likes this idea: https://botbot.me/freenode/elixir-lang/2015-05-27/?msg=40248917&page=7

Devin Torres

unread,
May 31, 2015, 6:19:55 PM5/31/15
to elixir-l...@googlegroups.com
Yeah, it's a very common pattern it feels like.

--
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 on the web visit https://groups.google.com/d/msgid/elixir-lang-core/15178352-82f1-402a-a982-c7c6a24a71b3%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Robert Virding

unread,
Jun 1, 2015, 12:47:03 AM6/1/15
to elixir-l...@googlegroups.com, de...@devintorr.es
Couldn't that make the syntax get rather messy when you mix?

Robert

Devin Torres

unread,
Jun 1, 2015, 1:21:13 AM6/1/15
to elixir-l...@googlegroups.com
I'm not sure. The ES6 code I've written has sometimes been mixed, and I don't think it's been messy so far--but I guess that's subjective.

Robert Virding

unread,
Jun 1, 2015, 3:11:53 AM6/1/15
to elixir-l...@googlegroups.com, de...@devintorr.es
Should the syntax allow writing

%Request{method, url: get_url(), headers, payload}

Robert

Dmitry Aleksandrov

unread,
Jun 1, 2015, 10:22:54 AM6/1/15
to elixir-l...@googlegroups.com, de...@devintorr.es
May be it is really cool syntax, I see only one problem, that: variable name becomes data. Does it good? (Do not have opinion, only ask about it)

Ramon Snir

unread,
Jun 1, 2015, 12:39:36 PM6/1/15
to elixir-l...@googlegroups.com, de...@devintorr.es
This is also what SML has: https://gist.github.com/psibi/4682181

Vincent Siliakus

unread,
Jun 1, 2015, 1:39:29 PM6/1/15
to elixir-l...@googlegroups.com, de...@devintorr.es
Something like this has been discussed here before:

https://groups.google.com/forum/#!topic/elixir-lang-core/4w9eOeLvt-8/discussion

In that discussion the consensus was that all 'field puns' (as they are apparently called) should be placed before regular fields, so your example would be only valid as:

%Request{method, headers, payload, url: get_url()}

If we still want this feature, I strongly agree with the 'field puns first' restriction, as it makes code arguably easier to read, less prone to errors and probably also easier to implement.

-vincent

Josh Adams

unread,
Jun 1, 2015, 3:04:18 PM6/1/15
to elixir-l...@googlegroups.com, de...@devintorr.es
Throughout the course of today I've had a bit of a change of heart on this.  I haven't used it personally, so maybe it is useful (and I know I type `thing: thing` in structs tons), but the fact that variable names become important definitely hits my "zomg-bone".  I worry that this feature makes code much harder to read and encourages a bit of magic that I don't much love.


--
Josh Adams

Peter Hamilton

unread,
Jun 1, 2015, 8:26:58 PM6/1/15
to elixir-l...@googlegroups.com, de...@devintorr.es
This is something fairly easily accomplished via macros, so we can use it for a bit without having to commit to it. I did something similar a while back with:

extract_vars_from_map(:foo, :bar, :baz, :qux) = params

was transformed to:

%{"foo" => foo, "bar" => bar, "baz" => baz, "qux" => qux} = params

It was fairly useful with Phoenix, as I was frequently unpacking all those values into variables.

Someone give it a shot, share the code and see if it's useful.

--
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.

Andrea Leopardi

unread,
Jun 3, 2015, 5:09:25 AM6/3/15
to elixir-l...@googlegroups.com, de...@devintorr.es
I'm with Josh on this one. Variable names having meaning may be confusing in my opinion; yes it's often tedious to do `foo: foo` but I prefer clarity over conciseness in this case.


On Sunday, May 31, 2015 at 12:19:46 PM UTC+2, Devin Torres wrote:

Drew Olson

unread,
Jun 3, 2015, 7:50:39 AM6/3/15
to elixir-l...@googlegroups.com, de...@devintorr.es
I don't like this. I prefer more explicitness even if it requires some more verbosity / name repetition in this case.

--
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.

Josh Adams

unread,
Jun 3, 2015, 9:36:22 AM6/3/15
to elixir-l...@googlegroups.com, de...@devintorr.es
On Wed, Jun 3, 2015 at 4:09 AM, Andrea Leopardi <an.le...@gmail.com> wrote:
yes it's often tedious to do `foo: foo` but I prefer clarity over conciseness in this case.

The worst example of this in my experience was an Angular app where we had a bit of scope value called 'data' stored in a value called data.  Passing this in with  the 'data-' prefix to make it pass html attribute parsing led to:

<widget data-data=data"/>

So 2 isn't so bad :)

Saša Jurić

unread,
Jun 3, 2015, 10:40:17 AM6/3/15
to elixir-l...@googlegroups.com, de...@devintorr.es
Assuming that %{foo, bar, baz, ...} is simply expanded into %{foo: foo, bar: bar, baz: baz, ...}, I like this proposal. I don't find it implicit nor magical, and I think it might make the code more readable in some cases.

I've occasionally experienced that foo: foo may add a lot of noise in functions which pattern match on maps, especially if I'm matching a couple of values and/or multiple maps. I think this shorter syntax might help with that.


On Sunday, May 31, 2015 at 12:19:46 PM UTC+2, Devin Torres wrote:

Bruce Tate

unread,
Jun 18, 2015, 8:33:46 AM6/18/15
to elixir-l...@googlegroups.com, de...@devintorr.es
This is going to be a big deal for Phoenix. In our channels where we're matching against the message, we have lines like this pervasively:

def handle_in(event, %{"chat" => chat, "question_id" => question_id, "data" => data, "attachment" => attachment, ...}, socket) do...
def handle_in(event, %{chat, question_id, data, attachment, ...}, socket) do...


It's the same model in controllers, like this:

def delete_question(conn, %{"survey_id" => survey_id, "question_id" => question_id, ...}) do
def delete_question(conn, %{survey_id, question_id, ...}) do

My guess is that this pattern is going to show up over and over in production elixir, as business concerns have more detail that we're going to want to move into structs and maps. We'll have to work out some details, like atoms versus strings, but we can support both pretty easily.  

I strongly support this proposal. To solve this problem, we're already moving toward macros (to keep things short, we're going to use a sigil, but it's distasteful and we'd prefer an in-language alternative. We really need a shorthand way to handle this concern. 

We will volunteer to take a pass at this solution, if we agree that this is a path that is interesting. 

Thanks, 
Bruce Tate (with Eric looking over my shoulder as we speak). 

José Valim

unread,
Jun 18, 2015, 8:42:42 AM6/18/15
to elixir-l...@googlegroups.com
This is going to be a big deal for Phoenix. In our channels where we're matching against the message, we have lines like this pervasively:

def handle_in(event, %{"chat" => chat, "question_id" => question_id, "data" => data, "attachment" => attachment, ...}, socket) do...
def handle_in(event, %{chat, question_id, data, attachment, ...}, socket) do...


It's the same model in controllers, like this:

def delete_question(conn, %{"survey_id" => survey_id, "question_id" => question_id, ...}) do
def delete_question(conn, %{survey_id, question_id, ...}) do


I have stayed mostly out of the discussion so I just want to point out that you are proposing something different than what was proposed so far. All the proposals were about %{survey_id, question_id} matching on *atom* keys. You are proposing to match on strings. And we need to pick one, we certainly cannot have the same syntax matching on both.

If we are sure we want both, then we need two syntaxes (and %{"survey_id", ...} doesn't cut it imo). We use sigils to handle a similar case, so we could adopt them here: 

    ~m(question_id survey_id)a #=> atom keys, the default
    ~m(question_id survey_id)s #=> string keys

But that is really up for everyone that have participated on this thread so far.

Peter Hamilton

unread,
Jun 18, 2015, 10:49:09 AM6/18/15
to elixir-l...@googlegroups.com

I'm opposed to a special syntax in Elixir itself. Aside from the confusion around two different behaviors (as Jose pointed out), there's even a third semantic case for %{foo,bar,baz} and that's "the map contains the keys (no assignments)".

I'm in favor of the sigil approach, or something similar, in a library. First, make it possible for people to use the syntax and then make it easy to use (ie, language builtin) as we get feedback from the community.

Just my 2 cents.


--
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.

Andrea Leopardi

unread,
Jun 19, 2015, 6:02:36 AM6/19/15
to elixir-l...@googlegroups.com, de...@devintorr.es
I stood against the %{foo, bar, baz} syntax and deemed that too implicit, but I'm in favour of a possible ~m sigil. I think it may still be a bit implicit, but I can see it would make code much more concise in a lot of situations. I took a stab at it and wrote a simple implementation for such sigil, here. We can try it out and see if we like it (and please, have a look at the implementation as well as I'm not sure it's bulletproof).



On Sunday, May 31, 2015 at 12:19:46 PM UTC+2, Devin Torres wrote:

Eric Meadows-Jönsson

unread,
Jun 19, 2015, 6:08:04 AM6/19/15
to elixir-l...@googlegroups.com
Nice work Andrea. One small comment; I think it would be nicer to default to string keys so that we have the same defaults as the ~w sigil.

It would also be nice to still be able to match on structs to match Devin's original example, possibly: ~m(Request method url headers payload). I don't think there is a nice way, with sigils, to match bound and unbound variables as in the second example.

--
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.

For more options, visit https://groups.google.com/d/optout.



--
Eric Meadows-Jönsson

Andrea Leopardi

unread,
Jun 19, 2015, 6:21:46 AM6/19/15
to elixir-l...@googlegroups.com
Hey, thanks! Defaulting on strings makes sense to match the default behaviour of the ~w sigil. Yes, matching structs would be nice and the syntax you proposed is probably the best (and only) option, even if I'm not a fan of it. What we could do though is to at least provide a third modifier, S, which specifies that ~m is matching a struct instead of a map, so that we don't take capitalized keys off the table and it's clear that the whole thing is a struct.

~m(Request method url headers payload)aS

What do you think?

Yes I don't think with sigils matching both bound and unbound variables is possible, but at the same time I don't think that use case is as common as matching on a map or struct and assigning variables.

Eric Meadows-Jönsson

unread,
Jun 19, 2015, 6:24:43 AM6/19/15
to elixir-l...@googlegroups.com
No extra modifier is required since there is no ambiguity for Request in ~w(Request foo bar). Variables cannot be capitalized so we can assume that Request is a module alias.


For more options, visit https://groups.google.com/d/optout.



--
Eric Meadows-Jönsson

Andrea Leopardi

unread,
Jun 19, 2015, 6:27:31 AM6/19/15
to elixir-l...@googlegroups.com
Right! Completely oversaw that, sorry :) will fix soon.

Thanks for the feedback!
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/NoUo2gqQR3I/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAM_eapjx_xvgM5kmYWBDScYDKciPDKyB_ztTrOuQhkJGzp307A%40mail.gmail.com.

José Valim

unread,
Jun 19, 2015, 7:40:26 AM6/19/15
to elixir-l...@googlegroups.com
Yeah, I disagree with the struct syntax. :) just keep it simple.

--


José Valim
Skype: jv.ptec
Founder and Director of R&D

Andrea Leopardi

unread,
Jun 19, 2015, 9:15:50 AM6/19/15
to elixir-l...@googlegroups.com, jose....@plataformatec.com.br
José, do you mean you disagree with supporting structs at all in the ~m sigil or just with the syntax?

Josh Adams

unread,
Jun 19, 2015, 9:24:39 AM6/19/15
to elixir-l...@googlegroups.com
I'm really happy with this.  I did see the duplicity as a problem but I thought the 'magic' would be confusing.  A sigil is an excellent compromise that brings all the goodness without the possible confusion.  :)

--
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.

For more options, visit https://groups.google.com/d/optout.



--
Josh Adams

José Valim

unread,
Jun 19, 2015, 4:43:50 PM6/19/15
to Andrea Leopardi, elixir-l...@googlegroups.com
I meant supporting structs in the ~m syntax, even more if it will default to strings.



José Valim
Skype: jv.ptec
Founder and Director of R&D

Johan Wärlander

unread,
Aug 10, 2015, 4:57:02 AM8/10/15
to elixir-lang-core, de...@devintorr.es
After playing around a bit with the ~m sigil, I proposed a couple of updates (one which touched on work that Andrea had alredy done, but with a twist); short_maps now support the following:

# Equality checks
foo = 1
bar = 2
~m(foo bar)a == %{foo: 1, bar: 2} #=> true

# Matching
~m(foo bar)a = %{foo: 12, bar: "baaz"}
foo #=> 12

# Pinning (NEW)
foo = 1
~m(^foo bar)a = %{foo: 1, bar: "baaz"} #=> %{foo: 1, bar: "baaz"}
~m(^foo bar)a = %{foo: 5, bar: "baaz"} #=> MatchError

# Structs, when first word starts with '%' (NEW)
defmodule Foo do
  defstruct bar: nil
end
~m(%Foo bar)a = %Foo{bar: "baaz"}
bar #=> "baaz"

For anyone else interested, please do get the latest version from Andrea's repository (https://github.com/whatyouhide/short_maps), then play around with it and see how it feels.

Jaap Frolich

unread,
Feb 17, 2016, 4:59:31 AM2/17/16
to elixir-lang-core, de...@devintorr.es
Very nice Johan,

Love the power of macros. Is there any resolution if this will make the standard library? I have a lot of repetition in my maps as well in my current app, and this is one of the things I like in ES6 that I really miss in Elixir (mostly it is of course the other way around ;)).

Cheers,

Jaap

Hassan Zamani

unread,
Aug 13, 2016, 6:23:56 AM8/13/16
to elixir-lang-core
I would appreciate hearing your thoughts on https://github.com/hzamani/synex. It has two macros: keys and params, first one for expansion of atom-value pairs in keyword lists, maps and structs, and other for expansion of string-value pairs in maps. Both of them have variable pinning and map update support and params supports nested maps:

iex> params(%{a => %{b, c}}) = %{"a" => %{"b" => 2, "c" => 3}}
iex> a
%{"b" => 2, "c" => 3}
iex> c
3

Hassan

Hassan Zamani

unread,
Aug 28, 2016, 1:13:55 PM8/28/16
to elixir-lang-core
No comments?

Ben Wilson

unread,
Aug 28, 2016, 2:38:55 PM8/28/16
to elixir-lang-core
You necro'ed a thread that was effectively a year old, it isn't an ideal place to receive feedback.

Maybe try posting on the elixir forum?

OvermindDL1

unread,
Aug 29, 2016, 12:31:36 PM8/29/16
to elixir-lang-core
Looks interesting, but how does it compare to https://hex.pm/packages/short_maps as it adds a sigil that does something similar?
```elixir
import ShortMaps

my_map = %{foo: 1, bar: 2, baz: 3}

~m(foo bar baz)a = my_map
foo #=> 1
import ShortMaps

name = "Meg"

# String keys by default (or with the 's' modifier)
~m(name) #=> %{"name" => "Meg"}
# Atom keys with the 'a' modifier
~m(name)a #=> %{name: "Meg"}
iex(1)> import ShortMaps
nil
iex(2)> name = "Meg"
"Meg"
iex(3)> ~m(^name)a = %{name: "Meg"}
%{name: "Meg"}
iex(4)> ~m(^name)a = %{name: "Megan"}
** (MatchError) no match of right hand side value: %{name: "Megan"}
```
And other stuff

Wiebe-Marten Wijnja

unread,
Aug 29, 2016, 12:40:48 PM8/29/16
to elixir-lang-core, de...@devintorr.es
Hmm... this is the kind of feature that I would've used a lot, had I known that a package that provided this shorthand syntax existed.
I will definitely do so in the future. I really like the sigil idea.

Are we still opposed to including the `~m` sigil in the base language?

OvermindDL1

unread,
Aug 29, 2016, 12:47:59 PM8/29/16
to elixir-lang-core, de...@devintorr.es
If it were included I would want it to be a bit more useful (and could perhaps be rolled into the map syntax itself with sigil, but I'm fine with it as a library):  https://github.com/whatyouhide/short_maps/issues/13

Hassan Zamani

unread,
Aug 30, 2016, 6:26:58 AM8/30/16
to elixir-l...@googlegroups.com, de...@devintorr.es
One of main differences of Synex and ShortMaps is that Synex supports nested stractures:
iex> params(%{a => %{b, c}}) = %{"a" => %{"b" => 2, "c" => 3}} iex> {b, c} {2, 3} iex> a %{"b" => 2, "c" => 3}

It supports map update syntax:
iex> person = %{"name" => "Jack", "age" => 26} iex> name = "John" iex> params(%{person | name, "age" => 28}) 
%{