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.
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’
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.
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.
> 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
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.
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.
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 I’ll do whatever a codebase’s style guide dictates I must do.
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.
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.
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 “_”.
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.
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.