Bob K
Bob K

Reputation: 69

Elixir: I'm getting an error from a simple addition function

I created a simple function in Elixir that increments a value from a tuple by 1.

I named the module Test and named the function addToTuple. It takes one argument, which is the tuple itself.

def addToTuple({X,Y}) do 
   {X,Y+1}
end

I compiled the module via iex and got this warning:

 warning: this expression will fail with ArithmeticError
  Test.ex:68

Line 68 refers to {X,Y+1}. When I run the function with the command Test.addToTuple({4,5}), I keep getting this error:

** (FunctionClauseError) no function clause matching in Test.addToTuple/1

    The following arguments were given to Test.addToTuple/1:

        # 1
        {4, 5}

    Test.ex:67: Test.addToTuple/1

I was expecting to get {4,6}.

Any idea what's going on here?

UPDATE 1:

I changed X and Y to lower-case and it worked. However, this time I modified the function a bit to play around with atoms:

def addToTuple({A,x,y}) do 
    {A,x,y+1}
end

Then I called the function with this command: Test.addToTuple({:F,4,5}). I was expecting to get {:F,4,6}. But instead, I got this error:

** (FunctionClauseError) no function clause matching in Test.addToTuple/1

    The following arguments were given to Test.addToTuple/1:

        # 1
        {:F, 4, 5}

    Test.ex:67: Test.addToTuple/1

I thought using an upper-case character would be treated as an atom? How can this be resolved?

Upvotes: 0

Views: 381

Answers (1)

7stud
7stud

Reputation: 48649

In your function definition here:

def addToTuple({A,x,y}) 

A is not a variable, rather A is an atom, which is like a constant. Normally, you write atoms like this:

 :dog
 :"my dog"

However, there is an alternative syntax for creating atoms: you can omit the leading colon and start the name with a capital letter, like this:

Dog

Elixir in Action(2nd) calls that an alias (for an atom). At compile time, Dog is converted to the atom :"Elixir.Dog". Check it out:

iex(5)> Dog == :"Elixir.Dog"
true

Back to your function definition:

def addToTuple({A,x,y})

The only arguments that will match your function definition look like this:

{A, 1, 3}
{A, "hello", "world"}
{A, [1, 2, 3], [4, 5, 6]}

In other words, the only thing that will "match" A in the function parameter list is A. Elixir is not like other languages where only variables are allowed in the parameter list for a function definition. In other languages, a function definition looks like this:

def go(x, y, z) do  
  ...
end

However, in elixir you can have constants in the parameter list for a function definition, like this:

def go(1, x, 2, y) do
  ...
end

In that function definition, the parameters include the constants 1 and 2. If you call that function like this:

go(10, 20, 30, 40)

the function won't execute because there is no match of the function arguments 10, 20, 30, 40 to the function parameters 1, x, 2, y. When you call a function, the function arguments are matched to the function parameters like this:

     go(10, 20, 30, 40)
        |   |   |   |
        V   V   V   V
 def go(1,  x,  2,  y) do

For that function call, elixir performs these matches/assignments:

    1 = 10
    x = 20
    2 = 30
    y = 40

Because 1 does not match 10, the function won't execute. The atom A is just like the integers 1 and 2. The only thing that will match A is A.

Because elixir allows constants in a function's parameter list, that allows you to define a series of function clauses like this:

defmodule My do

  def go(1, x) do
   IO.puts x*2
  end   
  def go(2, x) do
   IO.puts x-4
  end
  def go(A, x) do
   IO.puts x+5
  end

end

My.go(2, 3)
My.go(A, 5)

Output:

-1
10

When you call a function that has several clauses defined, elixir starts with the first clause and tries to match the arguments in the function call to the parameter list. If there is no match, elixir tries the next function clause, and so on. When a match is found, that function clause executes. If no match is found, elixir throws an error. For instance, the call:

My.go(B, 3)

results in:

** (FunctionClauseError) no function clause matching in My.go/2    

Another example:

defmodule My do

  def calc({:add, x, y}) do
    x + y
  end
  def calc({:subtract, x, y}) do
    x - y
  end
  def calc({:multiply, x, y}) do
    x * y
  end

end

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.calc {:add, 10, 5}
15

iex(2)> My.calc {:subtract, 10, 5}
5

iex(3)> My.calc {:multiply, 10, 5}
50

Upvotes: 5

Related Questions