Is there a shorthand version to destructure maps/structs in function definition

When defining a function where one want to destructure partially a map or struct we usually have

def func_name (%Person{first_name: first_name, last_name: last_name, current_position: current_position} = person) do
   ...
end

95% of the cases, we repeat the key names as vairables. Wouldn’t make sense to shorthand this like

def func_name( %Person{@first_name, @last_name, @current_position} = person) do
 ...
end
1 Like

This has been proposed (and implemented in libraries) multiple times. I think the core teams stance is that this won’t become part of elixir and for libraries you can read A story of regret and retiring a library from Hex – Andrea Leopardi

9 Likes

This is a very interesting article and it is great that the decision was to retire it, because one thing I hate about the JS ecosystem is the big number of bad and opinionated libraries.

It is funny how we, as developers, always tend to optimize the number of lines of code we write :smiley:.

3 Likes

Not so crazy about the number of lines in a function body.
But the defintion must be consize IMHO

1 Like

Move the definition to multiple lines? Of course it will be hard to read if you have a long single line with your definition.

José suggests to not use patterns for plain destructuring in a function head. Use it for pattern matching (where needed) and for any other fields use map.field in the function body or descructure in the function body.

19 Likes

@nikiiv if you’re interested in the community discussion there was a giant, well thought-out proposal posted here. I’m just sharing the link for your interest, I’m firmly on Team No-Shorthand.

6 Likes

At least I am not alone. TBH structs only without modifiers would be a killer fearure.
But alas… may be next decade.
Thanks a bunch for the link, a lot of interesting oppinions. IMHO the proposal failed because of the obscurity of the modifiers

1 Like

Lol, maybe!

You can check out Exstrcture (+ announcement/updates thread) which is a more recent package that looks quite well thought-out. It uses infix and unary operators and overloads ^ (albeit in a very intuitive way) so it depends on how you feel about that. It’s not something I’ve used myself, I was just impressed with the docs and design.

1 Like

I don’t think that’s quite true; at least that’s not the impression I got from my proposal a few months ago. I think it’s just not a feature anyone is championing to an agreeable conclusion, and has a lot of edge cases to think through that nobody is quite satisfied with solutions to.

1 Like

I’m on your team, too. Thanks for the link. I’m reading it now.

You can also use sigils for that:

1 Like

I could get behind the $ syntax, it’s a really interesting idea. While I would still prefer status quo (much prefer, I gotta say), having an operator on each entry makes a pretty huge difference to me. I also don’t agree with the line noise comment in the mailing list thread.

@Aetherus :metal:

@fceruti I recommended Exstructure because it offers what I perceive as a stronger API than the seemingly arbitrary choice for ~m for string and ~M for atom. I understand that these opinions wouldn’t mean much coming from someone who doesn’t use any of these libs, I just like the spirit of Exstructure :smiley: EDIT: I recognizing I’m side-stepping your point that modifiers were most likely not the reason for lack of adoption.

1 Like

i’ve been using shorter_maps for a while (and my own sigil based solution before that) and I’m very happy with it. it doesn’t do a lot, which I like, yet it improves readability in crucial places. a common pattern I use is

def handle_event("change_sorting", ~m{label}, socket) do
    ~M{current_user, business_id, search} = socket.assigns
[...]

and I still destruct in the body the vars I need as opposed to sticking everything in the definition.
once you get used to this it really becomes so much easier to read. but I do agree that ~M and ~m are so arbitrary it still annoys me a little bit. perhaps the only fault of the library.

oh man that’s pretty cool. love the flexibility. might try this out on a hobby project. overall I’m a little scared using this too much (or not enough) would make readability worse. just does too much. and yet being able to extract values without find functions seems wonderful.

1 Like

I agree. My take at the moment is that ruby’s syntax for this would probably be accepted. But, it just needs a champion to carry it across the finish line. Most of the time when this comes up in the mailing list Jose usually brings up the ruby approach.

That’s not my impression. In fact I tried an alternate approach where you could override the implementation of %{}, by excluding it from kernel imports. That was based on a suggestion by Jose. I tried that, but in order to do it, I had to port that code from Erlang to Elixir, which I was mostly able to do. But because of how Elixir bootstraps itself, it assumes a subset of the language is provided by Erlang and then builds the rest in Elixir, I was able to work out how to get it to boot correctly. I briefly worked with Jose on it at a conference and the conclusion was that there would be massive changes and it just may not be practical.

I dunno, maybe I should give some of the Elixir syntax ideas more of a chance. My dislike of punning comes from experience, though. I certainly have my opinions about syntax but I can mostly get used to anything reasonable—I’ve happily adopted mix format as my personal style despite disliking a good chunk of it at first. I used Prettier-enforced punning in JS it for 2.5 years and after all that time would still find myself getting tripped up sometimes at what I was looking at. Even my pro-punning teammates would too! Code scannability is a super high priority for me which punning kind of ruins (for me), at least in JS. But given a really obvious syntax I might change my tune (not that I have any influence over anything, lol).

As per your example, I often use frequent map destructing of the same things as a sign I’m missing a concept I could extract. There are a bunch of other factors there, of course (sorry for the hand-wavyness there but I have to get back to work, lol).

2 Likes

Interesting read, thanks for sharing.

89% of the people voted to add the feature, I’ve never seen this kind a support for anything!

Some people in the comments say there’s nothing to be gained, but oh man, if you can turn 15 line map destruct into a 2 liner, that would be huge for readability, not only “few less key strokes”.

1 Like

You’re looking at the poll of people who already support it! The second post has a poll where the majority is not in favour, albeit by a somewhat small margin.

This is where people lose me. If I need 15 keys from a map, I just use .. I find this infinitely more readable than having to read a whole setup first and then, it there are other variables in that setup (which there usually are), you lose track of what is part of a data structure and what wasn’t once you start reading the main body. Obviously this is just speaking from my own experience as I have only worked one project where this amount of destructuring was the norm and maybe we were just doing it poorly. I’ve had discussions about this before (though not related to destructuring) and I still don’t understand some peoples’ aversion to good ol’ ..

2 Likes

I was talking hyperbole with that 15 thing, so here’s a concrete example from a good code base

https://github.com/plausible/analytics/blame/ae0019afe50e84e8ed02c6db9dc8d96fee04c4a0/lib/plausible_web/live/goal_settings.ex#L12

Just focus on the header:

  def mount(
        _params,
        %{"site_id" => _site_id, "domain" => domain, "current_user_id" => user_id},
        socket
      ) do

5 lines where 1 would have done the job

def mount(_params, %{_site_id, domain, current_user_id}s, socket) do

At this point I’m bikeshedding because, despite my little rants, I fully support people coding how they want, but I’d write that like this:

def mount(_params, %{"site_id" => _} = session, socket) do
  %{"domain" => domain, "current_user_id" => current_user_id} = session

  # ...
end

I’m unsure if domain and current_user_id are important to the match, but that is all part of the reason I do it that way. Of course if they are important then you have a point, I’ve just never run into this situation.

3 Likes