What do you think about using `_` vs `_other`?

I am used to this syntax for a case structure (my question is about the _ -> part):

case {1, 2, 3} do
  {4, 5, 6} ->
    "This clause won't match"
  {1, x, 3} ->
    "This clause will match and bind x to 2 in this clause"
  _ ->
    "This clause would match any value"
end

A new team member introduced this into our project:

case {1, 2, 3} do
  {4, 5, 6} ->
    "This clause won't match"
  {1, x, 3} ->
    "This clause will match and bind x to 2 in this clause"
  _other ->
    "This clause would match any value"
end

So instead of a _ he/she uses _other. I do know that technically this is no different but I wonder what the majority does/thinks. I donā€™t want to tell that person in a code review that _other ā€œfeelsā€ funny in case everybody but me does it. Every time I see it I stumble upon it but I understand why one would use _other.

1 Like

In this case, I would use _ because the word ā€œotherā€ adds no additional context or meaning.

I do tend to prefer the named variant in function heads with multiple clauses. For instance:

def some_function(:explicit_match, _right), do: ...
def some_function(_left, :explicit_match), do: ...
def some_function(left, right), do: ...

This can be helpful especially with longer function heads that take, say, 4+ arguments, as multiple _ can get a little hard for me to parse visually.

12 Likes

Actually I did use _other a few times, but now that I think about it I probably just should have used _.

Itā€™s probably part of an inner monologue just like when people abuse the then function because it fits the inner monologue?

Just want to +100 this as itā€™s one of my biggest pet peeves. Like, no %#$@ itā€™s ā€œother.ā€ When it comes to scanning unfamiliar code, this type of thing is just distracting. If I see _some_var, I think that something useful is being communicated. As already said, _other as a catch-all is not at all useful.

And, technically they arenā€™t equivalent as _other gets bound whereas _ doesnā€™t. Iā€™m not advocating that is some sort of meaningful optimization, just sayinā€™ :upside_down_face:

3 Likes

Yeah, I dislike _other as well but many others are valuable i.e. you have a long chain of matching that checks for several types of structs / maps and then ultimately itā€™s fine if below you have _not_a_map -> ... ā€“ and variations of that. A proper unused variable name really helps code readability.

2 Likes

Absolutely, thatā€™s what I was trying to convey. Another way to look at it is that matching is basically (albeit not strictly) assignmentā€”you wouldnā€™t called a variable other (at least I hope you wouldnā€™t). So long as there is a name that makes sense I name my _s, which is the vast majority of the time.


To take this a bit further, due to Elixir being dynamic Iā€™d vouch for writing OPā€™s example like this (depending on context):

case tuple do
  {4, 5, 6} ->
    "This clause won't match"
  {1, x, 3} ->
    "This clause will match and bind x to 2 in this clause"
  {_, _, _} ->
    "This clause would match any 3-tuple"
end

and if nil is allowed, add a specific nil clause. This makes it very clear what is expected an errors out quickly if it gets junk.

With multiple clauses in pattern plain _ is even more important as:

case {1, 2} do
  {_other, _other} -> :doesnt_match
  {_, _} -> :match
end
4 Likes

Oh ya, good point!

Youā€™ll at least get a compiler warning if you do that, though.

EDIT: wait, no you wonā€™t since _other, _other binds as I even pointed out earlier :sweat_smile:

You actually do get a warning in this case:

> mix compile
    warning: the underscored variable "_other" appears more than once in a match. This means the pattern will only match if all "_other" bind to the same value. If this is the intended behaviour, please remove the leading underscore from the variable name, otherwise give the variables different names
    ā”‚
  4 ā”‚       {_other, _other} -> :doesnt_match
    ā”‚                ~
    ā”‚
    ā””ā”€ lib/compile_test.ex:4:16: CompileTest.my_fun/0
2 Likes

Well I should probably stop snap-responding then :upside_down_face:

2 Likes

Kinda surprised no one else pointed this out but if you use Credo on your project (and you should) then ā€“ unless youā€™ve suppressed the rule ā€“ it will give you a warning for just ā€˜_ā€™ and tell you to give it a more meaningful name.

Now ā€œotherā€ may not be the most descriptive variable name, but without better context of what sort of thing you are actually matching it will do in a pinch.

The TL;DR though is that Credo is the compendium of idiomatic Elixir choices and a bare underscore for a variable name is not idiomatic.

1 Like

Citation needed!

Credo is for enforcing per-project standards, not general community standards, and can be customized (which is what I do).

Also, check the root module module of Phoenix:

6 Likes

We practice documentation as code, so we encourage commenting ignored pattern matches like _error to better explain the variables:

case result do
  {:ok, data} -> do_action(data)
  _error -> fail()
end

If youā€™re literally talking about writing _other, thatā€™s something we would avoid and would try to come up with a better name since ā€œotherā€ is what _ -> means. The goal is to document the program, not the programming language.

2 Likes

In the spirit of documentation as code, I write:

case result do
  {:ok, data} -> do_action(data)
  {:error, _} -> fail()
end

This leaves little up to the imagination of an unsuspecting reader: they can see the exact shape and donā€™t have to look anything up. It also means we get a more meaningful exception if somehow we get nil or something else completely unexpected (the entire case fails the match as opposed to the catch-all failing the match). Even with safety nets we have, Elixir is still a dynamic language so anything is possible!

For me, a bare _ perfectly conveys ā€œI literally donā€™t care what I get here, even if it falls outside of the subset of things Iā€™m assuming itā€™s going to be.ā€ Otherwise, if youā€™ve been writing Elixir for more than a minute and you donā€™t know that the opposing match of an :ok tuple isnā€™t some form of :error then thatā€™s a whole other problem and naming your matches as such isnā€™t going to fix that.

I say that if you know the shape of the error, match on the shape! There is confidence in a bare _ that proclaims: ā€œI will literally accept any outcome!ā€ whereas _error sorta says ā€œWell, you know, Iā€™m assuming this is going to be an error tupleā€¦ like, probably.ā€

Please note, Iā€™m fully aware Iā€™m being way too overly passionate about a low-stakes stylistic point :upside_down_face: Iā€™ll do whatever a codebaseā€™s style guide dictates I must do.

1 Like

Hmmā€¦ I use Credo on pretty much everything and _ for placeholders both in function heads and to avoid Dialyzer unmatched return complaintsā€¦ But I donā€™t recall seeing the Credo warning you speak of. Are you sure this is stock/default Credo?

Not quite exact f.ex. Iā€™d also do {:error, _atom} for the sake of the future me (or another dev) who would want to do something with the value sometime in TheFutureā„¢. Could save them some time looking up the APIā€™s @spec-s.

Let me introduce you to Integer.parse/2 :003:

case Integer.parse(input) do
  {int, ""} -> "all good"
  {_int, _string_remainder} -> "oops, only partially an integer"
  :error -> "sorry, not an integer at all"
end

Ahh, itā€™s disabled by default. Definitely enabled at my current job (with about a dozen Elixir projects). Pretty sure it was enabled at my last job too though itā€™s been a few years now and hard to remember for sure.

https://hexdocs.pm/credo/Credo.Check.Consistency.UnusedVariableNames.html

Well, maybe thatā€™s my decade plus in Ruby-land showing through. Even though I left Ruby behind almost 6 years ago, I still have RuboCop PTSD.

Both RuboCop and Credo allow customizing rules but Iā€™ve always felt that there was a strong impetus to adopt the defaults and keep customization to a minimum. I definitely felt that way while working hard to keep RuboCop happy back in my Ruby days.

I mean, if you make Credo or RuboCop a PR check then you have to conform to the rules in order to get your work merged and complete stories. And I donā€™t think thatā€™s a bad thing (the RuboCop PTSD was because I felt that it went too far and was often a source of a lot of friction). Bike-shedding is best avoided at all costs. Consistency and readability of code saves a lot of mental overhead.

But, still, not everyone gets a say in the linter rules so you have to follow them and it sets a standard. But I think Iā€™m misremembering this one; a) itā€™s not on by default (as I indicated in another response here) and b) like the Phoenix example you shared, there is clearly plenty of important code out there still using bare ā€œ_ā€.

So Iā€™m changing the nature of my chiming in here: ā€œHey yā€™all, if you want to settle this and codify it in your codebase, thereā€™s a Credo check: Credo.Check.Consistency.UnusedVariableNames ā€” Credo v1.7.8ā€ :grinning:

2 Likes

Just to make it perfectly clear, this is not an optimization, not even a slight one. The Erlang compiler can see when a variable is unused and will emit the same code for matching _ as when matching _other or some other variable that is never used. Actually, to avoid having to handle _ as a special case, the Erlang compiler replaces _ with a freshly created variable that will never be used.

9 Likes

Ehn, fair, but I donā€™t think this adds too much. Thatā€™s itā€™s ā€œunwrappingā€ an error tuple is all the information I need, though I dunno, Iā€™ll think about it next time Iā€™m in this situation.

Well s :poop: t, fair enough!

Oh good to know, thank you!!

1 Like