Understanding Elixir functions with multiple clauses

elixir anonymous function multiple clauses
elixir function as argument
elixir function pattern matching
elixir cond
elixir guard clauses
elixir function capture
elixir syntax
elixir case vs function

I recently started learning Elixir. Coming from an object oriented programming background I am having trouble understanding Elixir functions.

I am following Dave Thomas's book Programming Elixir >= 1.6, but I do not quite understand how functions work.

In the book, he has the following example:

handle_open = fn
  {:ok, file} -> "Read data: #{IO.read(file, :line)}"
  {_,  error} -> "Error: #{:file.format_error(error)}"
end

handle_open.(File.open(​"​​code/intro/hello.exs"​))   ​# this file exists​
-> "Read data: IO.puts \"Hello, World!\"\n"

 handle_open.(File.open(​"​​nonexistent"​))           ​# this one doesn't​
 -> Error: no such file or directory"

I do not understand how the parameters work. Is there an implicit if, else statement hidden somewhere?

There are a couple of things going on here, and I'll try to cover all of them. For starters, there are two different functions being used here. One is the named function (File.open) and the other one is an anonymous function you created, assigned to the variable handle_open. There's a slight difference in the way both are called.

When you call the File.open function inside the handle_open function it, basically means you are calling handle_open on its result. But the File.open/2 function itself can return two values:

  1. {:ok, file} if the file exists
  2. {:error, reason} if it doesn't (or if there's another error)

The handle_open function uses pattern matching and multiple function clauses to check what the response was and returns the appropriate message. If the given value "matches a specified pattern" it executes that statement, otherwise it checks against the next pattern. Though in a sense, it is similar to an if-else statement, a better analogy is the case keyword:

result = File.open("/some/path")

case result do
  {:ok, file} ->
    "The file exists"

  {:error, reason} ->
    "There was an error"
end

Multi-clause Functions with Pattern Matching and Guards in Elixir, Multi-clause functions. One of the most interesting characteristics of Elixir is the fact that you can overload a function by specifying different clauses to match against. When you provide multiple clauses for a function with the same arity, this is known as multi-clause functions. One of the most interesting characteristics of Elixir is the fact that you can overload a function by specifying different clauses to match against. When you provide multiple clauses for a function with the same arity, this is known as multi-clause functions. We looked at the importance of function arity in Understanding Function Arity in Elixir.

In elixir, you can define functions with multiple clauses and elixir employs what's called pattern matching to determine which clause to execute. As you suspected, when you define multiple function clauses you effectively create an implicit if-else if, e.g. if the function arguments specified in the function call match the first function clause's parameters, then execute the first function clause, else if the function arguments specified in the function call match the second function clause's parameters, then execute the second function clause, etc.. Here is an example:

my.exs:

defmodule My do

  def calc(x, 1) do
    x * 3
  end
  def calc(x, 2) do
    x - 4
  end

  def test do
    IO.puts calc(5, 1)
    IO.puts calc(5, 2)
  end

end

(I typically do not leave a blank line between the multiple clauses of a function definition to indicate that they are all the same function.)

In iex:

$ iex my.exs
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> My.test
15
1
:ok

iex(2)>

When you call calc(5, 1) elixir goes to the definition of calc(), and elixir starts with the first function clause and tries to match the args 5, 1 against the parameters x, 1. Because the parameter x is a variable, it can match anything, while the parameter 1 can only match 1. Therefore, there is a match and x gets assigned the value 5. In other languages, you can't have values, like 1, "hello",%{a: 1} or :error, specified as a function parameter in a function definition--rather all the function parameters have to be variables.

Similarly, when you call calc(5, 2), elixir goes to the definition of calc(), and elixir starts with the first function clause and tries to match the arguments 5, 2 in the function call against the parameters x, 1. The x parameter can match anything but the 1 parameter does not match the 2 argument, so elixir goes to the next function clause and tries to match the arguments 5, 2 against the parameters x, 2. That produces a match and x gets assigned 5.

Elixir has some pretty interesting rules for matching. Here are a couple of rules that you will come across at some point, which can be hard to decipher:

  1. def func(%{] = x) will match any argument that is a map--not just an empty map--and the map will get assigned to the variable x. The same goes for structs, e.g. def func(%Dog{} = x) will match any Dog struct.

  2. def func("hello " <> subject) will match any string argument that starts with "hello ", and the rest of the string will be assigned to the subject variable.

Elixir also allows you to define anonymous functions with multiple clauses. If you change test() to:

  def test do
    func = fn 
              (:ok, x)    -> IO.puts ":ok branch, x = #{x}"
              (y, :error) -> IO.puts ":error branch, y = #{y}"
           end

    func.("hello", :error)
    func.(:ok, 10)
  end

then in iex you will see:

~/elixir_programs$ iex my.exs
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> My.test()  
:error branch, y = hello
:ok branch, x = 10
:ok

iex(2)> 

The matching works just like with named functions: elixir looks at the function clauses in order and tries to match the arguments in the function call against the parameters in the function clauses. Note that the order in which you define the function clauses in your code can matter. It's easy to define a function clause that will never execute, for instance:

func = fn 
          (x)    ->  ...
          (:ok)  ->  ...
       end

Because the parameter x in the first function clause will match any argument, the second function clause can never execute. Luckily, elixir will warn you if you do that.

And, because elixir is a functional language it would be remiss to not show a recursion example. Add the following definition of count() to the My module:

  def count(0) do
    :ok
  end
  def count(n) when n > 0 do
    Process.sleep 1_000  #sleep for 1 second
    IO.puts n
    count(n-1)
  end

Then in iex:

 ~/elixir_programs$ iex my.exs 
 Erlang/OTP 20 [erts-9.3] [source]
 [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe]
 [kernel-poll:false] Interactive Elixir (1.6.6) - press Ctrl+C to exit
 (type h() ENTER for help)

iex(1)> My.count(10)
10
9
8
7
6
5
4
3
2
1
:ok

iex(2)>

When the code calls count(n-1), elixir goes to the first function clause of count(), then tries to match whatever the value of the argument n-1 is against the parameter 0 in the function clause. If there's no match, elixir tries the second function clause. As a result, the second function clause keeps matching the function call count(n-1) until all the numbers including 1 are output, whereupon n-1 is equal to 0 in the function call, resulting in the function call count(0), which finally matches the first function clause, and the first function clause doesn't output anything nor does it call itself, so the recursion ends.

The key things to learn in elixir are:

  1. Pattern matching
  2. Recursion
  3. Spawning other processes

Good luck!

Modules and functions, When you have multiple function clauses that have the same number of parameters and one (or more) of the parameters has a default value,  If a function has several clauses, Elixir will try each clause until it finds one that matches. Here is an implementation of a function that checks if the given number is zero or not: defmodule Math do def zero?

A good explanation for work with files is this book: joyElixir. It's a good place for start and is a small book.

Functions · Elixir School, A version of a function is referred to in Elixir as a "function clause". A single function can have multiple clauses. For example, let's look at this  If you're defining a function with multiple clauses and a default value, the default value definition belongs in a function head. Learn more about default arguments, function heads and function clauses in this Elixir School lesson. Why is it Useful? Recursion is useful anytime we need to repeat an action under a certain condition.

Learn With Me: Elixir, warning: definitions with multiple clauses and default values require a header. I don't understand what the warning is trying to get me to do. This function head has the default arguments on it so that the ambiguity goes  In Elixir, you can have multiple clauses for the same function with pattern matching and guard clauses. Thanks to that, you can dynamically dispatch different expressions given different data types. But the downside is it’s hard to figure out in a quick glance how many data types are supported.

Learn With Me: Elixir, If you have a function with multiple clauses and defaults you need to define a '​function head' to define Do you see what is going on here? If you’re defining a function with multiple clauses and a default value, the default value definition belongs in a function head. Learn more about default arguments, function heads and function clauses in this Elixir School lesson. Why is it Useful? Recursion is useful anytime we need to repeat an action under a certain condition.

Definitions with multiple clauses and default values , The case construct lets you perform pattern matching inside of your function clause. If you found the multiple function clauses of Example 3-2 hard to read, you  The & Shorthand. Using anonymous functions is such a common practice in Elixir there is shorthand for doing so: iex> sum = &(&1 + &2) iex> sum.(2, 3) 5. As you probably already guessed, in the shorthand version our parameters are available to us as &1, &2, &3, and so on.

Comments
  • one important point that i want to share with you is: in anonymous functions you can not mix clauses with different arities, example fun = fn x,y -> "TEST" ; x,y,z -> "TEST2"; end you will have an error: ** (CompileError) iex:1: cannot mix clauses with different arities in anonymous functions
  • If you want to know what values the function return, use IO.inspect (functionx()) examples: handle_open.(IO.inspect (File.open(​"​​code/intro/hello.exs"​))) or with pipe operator, example: File.open(​"​​code/intro/hello.exs"​) |> IO.inspect |> handle_open.()
  • Check this link about anonymous functions link: blog.distortedthinking.agency/articles/…