Could you tell me how to solve unsafe warning for "receive"?

Warning issues of unsafe often happen and how to solve the following case:

def dine(phil, table) do
  send table, {:sit_down, self(), phil}
  receive do 
    {:eat, forks} ->
      phil = eat(phil, forks, table)
      phil = think(phil, table)
      # dine(phil, table)
  end
  dine(phil, table)       #=> "variable phil is unsafe" 
end

warning: the variable “phil” is unsafe
version: Elixir 1.5.0
When dine() moves to comment #dine, it is OK. But I want to keep the above code steps.

Could you help me how to write code to solve this?

Thanks!

1 Like

Try:

def dine(phil, table) do
  send table, {:sit_down, self(), phil}
  new_phil =
    receive do 
      {:eat, forks} ->
        phil
        |> eat(forks, table)
        |> think(table)
    end
  dine(new_phil, table) 
end

Remember: expressions, not statements.

I suspect that the warning was issued because you were referencing a name (phil) that was being rebound inside one of the receive pattern blocks (of which there can be many, not all of them necessarily providing a value for phil - even when in your particular case there doesn’t seem to be a problem).

By making new phil the (return) value of the receive expression any potential ambiguity is resolved.

5 Likes

Thanks for leading me to correct.

Are there any ways that I don’t need new_phil = receive do ... ?
In other case of if statement, I am annoyed.

new_phil 
    = if expression do
          # process ... 
          phil = xxxx
       else 
          phil
       end

else statement bothers me a lot as I have to add it.
Or I might as well ignore the warnings?

1 Like

Well considering that if is an expression, what would you expect it to return if false? :wink:

Also, don’t do phil = xxxx, just do xxxx.

2 Likes

There is always the option to refactor:

def eat_with_forks (phil, table) do
  receive do 
    {:eat, forks} ->
      phil
      |> eat(forks, table)
      |> think(table)
  end
end

def dine(phil, table) do
  send table, {:sit_down, self(), phil}
  phil 
  |> eat_with_forks(table)
  |> dine(table)
end

In other case of if statement, I am annoyed.

There is no equivalent to C’s:
if '(' expr ')' stmt [ else stmt ]

The if in Elixir is like the ternary or conditional operator:

logical-OR-expression ? expression : conditional-expression

so there always has to be an else - if you leave out the else it assumes:

logical-OR-expression ? expression : nil

which may not be what you want.

Furthermore between the if and the else and the else and the end isn’t a block of statements - it’s a sequence of expressions; the value of that sequence is the value of the last expression in the sequence - the values of the preceding expressions are ignored which means they are executed for their side-effect (which often is binding the value to a name).

Finally = isn’t an assignment - it’s a match - the name(s) on the left hand side is(are) bound to a value(values) that will make it(them) match to the right hand side (if that is possible - see the pin operator).

Also see:

3 Likes

I appreciate your kind explanation and I am impressed at your code of eat_with_forks regards receive do.
I am learning Elixir with reading project examples and refactoring on my own way. Most of codes I got from github have warnings and old functions such as Dict.map, though it must be good practice to correct them.

Regards if statement, I don’t reach the level of that I completely understand. But I know what result I can get by experiments.

I am learning and need your help, anyway.
Thanks a lot.

1 Like