Scope of variable passed to function

Hi,
I am wondering why my solution wil not work

1. source: Perfect Numbers in Elixir on Exercism

2. solution attempt:

  @spec classify(number :: integer) :: {:ok, atom} | {:error, String.t()}
  def classify(number) do
    case aliquot_sum(number) do
      number -> ok(:perfect)
      x when x > number -> ok(:abundant)
      x when x < number -> ok(:deficient)
    end
  end

  defp ok(item), do: {:ok, item}
  defp aliquot_sum(number), do: get_divisors(number) |> Enum.sum()

  defp get_divisors(number, divisor \\ 1, acc \\ [])
  defp get_divisors(number, divisor, acc) when divisor > div(number,2), do: acc
  defp get_divisors(number, divisor, acc) when rem(number, divisor) == 0,
    do: get_divisors(number, divisor + 1, [divisor | acc])
  defp get_divisors(number, divisor, acc), do: get_divisors(number, divisor + 1, acc)
end

3. question:

Complier is saying:

this clause cannot match because a previous clause at line 15
always matchesElixir
perfect_numbers.ex(15, 7): related

and

variable "number" is unused (there is a variable with the same name in the
context, use the pin operator (^) to match on it or prefix this variable 
with underscore if it is not meant to be used)Elixir

Why is that, why it cannot match with number passed to function?

The first case clause matches any number. The clauses’ order matters.

But I used the same variable name in function clause, so I would expect that It would match against it

Oh, I get you now. You can use the pin operator ^number.

1 Like

No, your logic is wrong and number might not be what You think…

As soon as there is a match, the rest of the case is discarded

the number in the case is not the number parameter, it is a new number

You probably should write this with cond

aliquot = aliquot_sum(number)
cond do
  aliquot > number -> ...
  aliquot < number -> ...
  aliquot == number -> ...
end
1 Like

After adding ^ operator it’s right why should I use cond? What is more “idiomatic” whatever this “idiomatic” word mean? :smiley:

Because it’s very rare to use ^number in the case :slight_smile:

Yet, You can do as You like

In my experience it’s very rare to use cond but it’s still valid.

Last question, i cannot find explanatio why this works that way. Do you have explanation or link to resources?

https://hexdocs.pm/elixir/pattern-matching.html#the-pin-operator

People have a weird aversion to cond for some reason that I don’t really understand. Doing pure boolean checks is exactly what it exists for. The case version is essentially the same with unnecessary extra syntax. Using cond gives added information that indeed all clauses are boolean checks. It’s only rare because this doesn’t come up as often in Elixir, but it certainly does come up!

Ok, I get it, but the question was, why it was not working with case this, matching why do we have to use this ^ pin operator?

Oh sorry wasn’t responding specifically to your question.

But to answer it: Elixir allows variable re-binding, ie, you can say: number = number+ 1 and not get an error that number is already defined. Like =, -> is also a binding operator, so when you say number -> ok(:perfect), number is rebound. The match on the value of number, you need to use the pin operator.

1 Like

This form rebinds the name number inside the case branch:

def some_fun(number) do
  case 2*number do
    number ->
      # number here is bound to a new value, shadowing the argument named number

This will match ANY integer passed to some_fun.

ON THE OTHER HAND

Using the pin operator says “match this existing value”:

def some_fun(number) do
  case 2*number do
    ^number ->
      # number here is still bound to the argument

That will only match if some_fun is passed 0, since 2*0 == 0

2 Likes