I don’t want to speak for the whole community, but at least for me, the example with nil is simple, just use the shorter version.
When it comes to tuples, there is a semantic difference. The guard will accept any tuple, no matter its size. Usually we know what size of tuple the function expects, so I would put that expectation into the pattern match.
In other words, when I see value when is_tuple(value), I think there is a reason why the code needs to accept tuples of different (and possibly unknown) sizes.
Also, since guards allow for more advanced logic, I guess I prefer pattern matching on exact values because of the simplicity.
This right here is half the reason I can’t stand elem in pipelines and really just elem in general. Unless you really are dealing with a tuple of an unknown size—which does happen but is rare—pattern matching is always clearer, pipeline be dammed
I agree with the “go with the simpler one” logic, which I would extend to mean “the quickest to read and understand.”
This can be more debatable in cases like the following:
def foo(bar1, bar2) when bar1 == bar2, do: "bar!"
vs
def foo(bar, bar), do: "bar!"
There is no community standard around this, though. It’s on a per project basis.
Preference is for pattern matching. I used guards to pre-filter before pattern matching if needed. I suspect (but don’t know for certain) that pattern matching is likely a bit more computationally efficient. It’s certainly more precise.
I heard people say that we should use guards for general type checks and pattern-matching for destructuring data and asserting on its shape and I generally agree with that and use them that way. They obviously overlap sometimes e.g. you can do both of these:
def stuff(param) when is_map(param), do: ...
or
def stuff(%{} = param), do: ...
But I avoid the second form if there are no strict rules about the shape of the map itself.
So if you have those requirements – use pattern-matching.
Tangential to the conversation, but for this one, the following has worked in the past:
# Not very clear, folks with less experience (or you some time :) ) can scan past this
def foo(bar, bar), do: "bar!"
# fairly clear, but forgoes pattern matching, we should use it :)
def foo(bar1, bar2) when bar1 == bar2, do: "bar!"
# has worked in the past, I'd prefer seeing this in the codebase over all the others
def foo(same_bar, same_bar), do: "bar!"
@krisleech, always pattern match if possible (my opinion, ymmv). The only drawback is that once you see problems with pattern matching in mind you can’t use another language
lol I actually had exactly this (as this is how I do it) but decided to change it to bar to illustrate my point a little better.
Furthermore, I think there are situations where one is clearer than the other. For a simple comparison like that I prefer guards (even though just yesterday I opted for a (same, same)). But if you needed to check multiple values, matching can be nicer since a == b == c doesn’t work in Elixir.