'mix format' and grouping maps by an atom

I’m trying to figure out the idiomatic way to group a list of maps by an atom because mix format gives me an interesting result, depending on what I try.

Here’s my input and I’d like to group by :species.

iex(7)> characters = [                                   
...(7)>   %{species: :human, name: "Calvin"},
...(7)>   %{species: :human, name: "Rosalyn"},       
...(7)>   %{species: :feline, name: "Hobbes"}       
...(7)> ]

My first approach was to be explicit by using Map.get/3:

iex(8)> characters |> Enum.group_by(&Map.get(&1, :species))
%{
  feline: [%{name: "Hobbes", species: :feline}],
  human: [
    %{name: "Calvin", species: :human},
    %{name: "Rosalyn", species: :human}
  ]
}

That looks pretty good.

And then I tried it with a combination of capturing and map.key syntax*, getting the same result. :sunglasses:

iex(9)> characters |> Enum.group_by(&(&1.species))

Now for the interesting part: when the latter example is subjected to mix format the result is:

characters |> Enum.group_by(& &1.species)

To my novice eyes, that (& &1.species) part looks really weird and now I’m thinking I’m barking up the wrong tree. :thinking:

I’d appreciate any thoughts. Thanks!


Extra stuff:

I found this (really cool) reply by Jose, but in this case the capture involves a tuple and mix format doesn’t change it:

(* I understand that map.key will raise when Map.get/3 will nil, and therefore they’re not exactly the same.)

Enum.group_by(&(&1.species)) is exactly equivalent to Enum.group_by(& &1.species). The parens for & are always optional. The formatter removes them because they’re unnecessary. It doesn’t remove the {} in Jose’s example because those are part of the actual datastructure definition.

1 Like

Thanks for clarifying, Ben.

(And I’m really looking forward to digging into your book once I get more Elixir fundamentals down. I’ve been writing a fair number of RESTful APIs in Java lately and, well… seeing something new will be very cool. :wink:)

I also took a look at the doc for Kernel.&/1 and that helped solidify the fact that it’s just another macro and nothing magical is going on with the invocation.

So, what I gather is that Enum.group_by(& &1.species) won’t look so weird to me as I continue to develop my Elixir eyes and this is indeed idiomatic.

1 Like

It’s not. The macro beside the source link is misleading - the source it links to is a macro but that isn’t what helps to implement the described behaviour:

defmacro unquote(:&)(expr), do: error!([expr])

That would generate an error if it evaluated during compilation.

Kernel.SpecialForms:

Special forms are the basic building blocks of Elixir, and therefore cannot be overridden by the developer.

So implementing Kernel.SpecialForms.&/1 is built into the compiler.

2 Likes

Well, fundamentally it’s syntatictally a macro, which is what’s relevant here. It follows the same syntax calling rules as other macros in that the formatter can choose to elide parens by default, as it does with if, def, and other macros.

2 Likes

Thanks for clarifying that it is indeed a special form; I was a little confused by the source that I saw.

1 Like