Functions call with underscore and assignment

While watching Jose Valim doing advent of code 2021, I noticed him called function like this

def test(a) do
    IO.puts(a)
end
test(_a = 5)

What is the purpose of using _a = 5 instead of 5? Is there a name for this technique?

useful to not get lost when you have mutiple arguments, eg

iex(1)> foo = fn a,b,c,d -> {a,b,c,d} end
#Function<41.65746770/4 in :erl_eval.expr/5>
iex(2)> foo.(_a=1, _b=2, _c=3, _d=4)
{1, 2, 3, 4}

Note that this is different to eg pythons named arguments, where the names really have a meaning.
in elixir its just to help read the code and has no meaning.

iex(3)> foo.(_banana1=1, _banana2=2, _banana3=3, _banana4=4)
{1, 2, 3, 4}

You could also do

iex(4)> foo.(a=1, b=2, c=3, d=4)                            
{1, 2, 3, 4}

But’ll get a warning, therefore the _
But you’ll (re)bind the variables which is most likely not what you want.

see https://elixir-lang.org/getting-started/pattern-matching.html

In some cases, you don’t care about a particular value in a pattern. It is a common practice to bind those values to the underscore, _ . For example, if only the head of the list matters to us, we can assign the tail to underscore:

3 Likes

nice trick!

Nice one too. I didn’t know you could define variables in function calls.

1 Like

Actually, I also did not know that.
I thought you’d get an unused variable warning, but indeed:

defmodule Temp do
  def foo(a, b, c, d), do: IO.inspect({a, b, c, d}, label: :in_foo)

  def bar() do
    foo(a=1, b=2, c=3, d=4)
    IO.inspect({a, b, c, d}, label: :after_foo)
  end
end
iex(11)> Temp.bar   
in_foo: {1, 2, 3, 4}
after_foo: {1, 2, 3, 4}
{1, 2, 3, 4}

So the _ is used here to hint, that the variable should be ignored.

But still, you’ll get the same result when changing the variable names in the call:

def bar() do
  foo(k=1, l=2, m=3, n=4)
  IO.inspect({k, l, m, n}, label: :after_foo)
end

while in python for example:

>>> def foo(a, b):
...    print(f"first {a} then {b}")
... 
>>> foo(1,2)
first 1 then 2
>>> foo(a=1,b=2)
first 1 then 2
>>> foo(b=1,a=2)
first 2 then 1
>>> foo(c=1,d=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() got an unexpected keyword argument 'c'
1 Like