I’d like to write a function that receives all messages in the inbox and returns them as a list. It should be a dead-simple task, but I can’t quite trace the recursion.
def receive_messages(timeout) do receive do data -> case receive_messages(timeout) do {:ok, next_data} -> {:ok, [data | next_data]} :empty -> {:ok, data} end after timeout -> IO.puts("No messages in inbox.") :empty end end
Expected Output (If There Are Messages)
{:ok, ["Message from #1.", "Message from #2.", "Message from #3."]}.
Actual Output (If There Are Messages)
{:ok, ["Message from #1.", "Message from #2." | "Message from #3."]}.
There’s something fishy about my receive_messages/1, but I just can’t fix it. I should probably split the function into several cases, too, but I couldn’t figure out how they should be structured.
Your :empty case should probably return a list as data :
def receive_messages(timeout) do receive do data -> case receive_messages(timeout) do {:ok, next_data} -> {:ok, [data | next_data]} :empty -> {:ok, [data]} # here end after timeout -> IO.puts("No messages in inbox.") :empty end end
Otherwise, your last next_data will not be a list, creating "Message from #2." | "Message from #3." by concatenation :
def receive_messages(timeout, acc \\ []) do receive do msg -> receive_messages(timeout, [msg | acc]) after timeout -> case acc do [] -> :empty _ -> {:ok, :lists.reverse(acc)} end end end
Also note that if you can pattern match on the empty list outside of the function. Sometimes it does not make sense to have :empty as a special case.
So that could be:
def receive_messages(timeout, acc \\ []) do receive do msg -> receive_messages(timeout, [msg | acc]) after timeout -> {:ok, :lists.reverse(acc)} end end
And it just returns {:ok, []} if no messages were received.
And now, since it cannot return {:error, _} or :empty I would just remove the tuple and return a list:
def receive_messages(timeout, acc \\ []) do receive do msg -> receive_messages(timeout, [msg | acc]) after timeout -> :lists.reverse(acc) end end
That’s true. I removed it and the code’s cleaner for it. I don’t think I can pattern-match on an empty map, but I can simply use Enum.empty?/1. I’d probably have used an if statement regardless.
def dispatch() do timeout = 10 messages = receive_messages(timeout) if not Enum.empty?(messages) do # ... end Process.sleep(1000) dispatch() end
Nope. I’ll try to pop a single item off the enumerable and if there is one returns false otherwise the enumerable is empty. It doesn’t enumerate everything.
Even if it’s constant time it’s still more work though:
map = for i <- 1..5000, into: %{}, do: {i, System.unique_integer()} Benchee.run(%{ "map_size" => fn -> map_size(map) > 0 end, "pattern" => fn -> map != %{} end })
Operating System: Linux CPU Information: AMD Ryzen 3 3200G with Radeon Vega Graphics Number of Available Cores: 4 Available memory: 5.80 GB Elixir 1.13.2 Erlang 24.1.7 Benchmark suite executing with the following configuration: warmup: 2 s time: 5 s memory time: 0 ns reduction time: 0 ns parallel: 1 inputs: none specified Estimated total run time: 14 s Benchmarking map_size ... Benchmarking pattern ... Name ips average deviation median 99th % pattern 1.45 M 0.69 μs ±5168.36% 0.47 μs 0.96 μs map_size 0.93 M 1.07 μs ±2783.37% 0.77 μs 1.65 μs Comparison: pattern 1.45 M map_size 0.93 M - 1.56x slower +0.38 μs