Reputation: 12085
I see functions that enclose a receive block getting called recursively seemingly not in the tail position all over the place in elixir example code. For example:
defmodule A do
def loop do
receive do
:ping ->
IO.puts "Received :ping"
loop # <- Tail position?
:pong ->
IO.puts "Received :pong"
loop # <- Also Tail position?
after
5000 ->
loop # <- Also Tail position?
end
loop # <- Also Tail position?
end
end
Is receive a special construct that optimizes for tail positions at the end of all match blocks? If so does it apply if the receive block has an after
block? What if there is code after the receive block in the function?
Upvotes: 1
Views: 451
Reputation: 54684
In your example, only the very last call to loop
is tail recursive. Instead of making the recursive call inside the receive
block, just let the receive
return and just use the tail-recursive call to loop at the end of the function that is already there.
defmodule A do
def loop do
receive do
:ping ->
IO.puts "Received :ping"
:pong ->
IO.puts "Received :pong"
after
5000 ->
nil
end
loop
end
end
As a general rule, process loops should always have exactly one recursive call at the end of the loop
function. That's the simplest way to guarantee that the process will be able to loop infinitely.
However, you can loop inside the receive
block if it is the last statement in the loop
function. The result will also be tail-recursive. This allows you to conditionally stop the loop based on a received message
defmodule A do
def loop do
receive do
:ping ->
IO.puts "Received :ping"
loop
:pong ->
IO.puts "Received :pong"
loop
:stop ->
IO.puts "Bye Bye"
end
end
end
Also see http://erlang.org/doc/efficiency_guide/processes.html#id69762
Upvotes: 5