3
votes

I see this code in the example of Elixir:

defmodule Recursion do
  def print_multiple_times(msg, n) when n <= 1 do
    IO.puts msg
  end

  def print_multiple_times(msg, n) do
    IO.puts msg
    print_multiple_times(msg, n - 1)
  end
end

Recursion.print_multiple_times("Hello!", 3)

I see here the same function defined twice with different arguments, and I want to understand this technique.

Can I look at them as at overloaded functions?

Is it a single function with different behavior or are these two different functions, like print_only_once and print_multiple_times?

Are these functions linked anyhow or not?

2
It's really about pattern matching and guard clauses. The function implementation which best fits the arguments is chosen, which includes evaluation of the when guard clause. It's the same function, it merely has multiple implementations.deceze
Whetever your function has different signature, it's ok, but remember- different access (def or defp) is not allowed when signature is the same.PatNowak

2 Answers

3
votes

Usually in functional languages a function is defined by clauses. For example, one way to implement Fibonacci in an imperative language would be the following code (not the best implementation):

def fibonacci(n):
  if n < 0:
    return None
  if n == 0 or n == 1:
    return 1
  else:
    return fibonacci(n - 1) + fibonacci(n - 2)

To define the function in Elixir you would do the following:

defmodule Test do
  def fibonacci(0), do: 1
  def fibonacci(1), do: 1
  def fibonacci(n) when n > 1 do
    fibonacci(n-1) + fibonacci(n - 2)
  end
  def fibonacci(_), do: nil
end

Test.fibonacci/1 is only one function. A function with four clauses and arity of 1.

  • The first clause matches only when the number is 0.
  • The second clause matches only when the number is 1.
  • The third clause matches with any number greater than 1.
  • The last clause matches anything (_ is used when the value of the variable is not going to be used inside the clause or is not relevant for the match).

The clauses are evaluated in the order they are declared, so for Test.fibonacci(2) will fail in the first 2 clauses and match the third one because 2 > 1.

Think of clauses as a more powerful if statement. The code looks cleaner this way. And is very useful for recursion. For example, a map implementation (the language already provide one in Enum.map/2):

defmodule Test do
  def map([], _), do: []
  def map([x | xs], f) when is_function(f) do
    [f.(x) | map(xs, f)]
  end
end
  • First clause matches an empty list. No need to apply a function.
  • Second clause matches a list where the first element (head) is x and the rest of the list (tail) is xs and f is a function. It applies the function to the first element and recursively calls map with the rest of the list.

Calling Test.map([1,2,3], fn x -> x * 2 end) will give you the following output [2, 4, 6]

So, a function in Elixir is defined with one or more clauses where every clause have the same arity as the rest.

I hope this answers your question.

1
votes

In the example you posted both definitions of the function have the same number of arguments: 2, this "when" thing is a guard, but you can also have definitions with many arguments. First, guards -- they are uses to express what cannot be written as a mere matching, like the second line of the following:

def fac(0), do: 1
def fac(n), when n<0 do: "no factorial for negative numbers!"
def fac(n), do: n*fac(n-1)

-- since it's not possible to express being negative number by just equality/matching.

Btw this fac is a single definition, only with three cases. Notice the coolness of using constant "0" in the position of argument :) You can think of this as it would be nicer way to write:

def fac(n) do
  if n==0, do: 1, else: if n<0, do: "no factorial!", else: n*fac(n-1)
end

or a switch case (which even looks pretty close to the above):

def fa(n) do
  case n do
    0 -> 1
    n when n>0 -> n*fa(n-1)
    _ -> "no no no"
  end
end

only "looks more fancy". Actually it turns out certain definitions (e.g. parsers, small interpreters) look much better in the former than latter style. Nb guard expressions are very limited (I think you can't use your own function in guard).

Now the real thing, varying number of arguments -- check this out!

def mutant(a), do: a*a
def mutant(a,b), do: a*b
def mutant(a,b,c), do: mutant(a,b)+mutant(c)

e.g.

iex(1)> Lol.mutant(2)
4
iex(2)> Lol.mutant(2,3)
6
iex(3)> Lol.mutant(2,3,4)
22

It works a bit similar like (lambda arg ...) in scheme -- think of mutant as taking all its arguments as a list and matching over it. But this time, elixir treats mutant as 3 functions, mutant/1, mutant/2, and mutant/3 and will refer to them as such.

So, to answer your question: these are not like overloaded functions, but rather scattered/fragmented definitions. You see similar ones in functional languages like miranda, haskell or sml.