dsaghliani
How can I receive all messages in the inbox and return them as a list?
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.
Marked As Solved
lud
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
Also Liked
antoine-duchenet
Hi,
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 :
iex(1)> ["2" | "3"]
["2" | "3"]
iex(2)> ["2" | ["3"]]
["2", "3"]
LostKobrakai
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
lud
I would go with tail recursion on this one:
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
dsaghliani
Yes, that’s a lot simpler! I was having trouble following my own implementation.
I was planning to replace my use of a list—which was simpler to prototype—with a map, and I had an easier time with your code. Thank you.
lud
If your function returns a map then you can use if map_size(map) > 0
In my examples, the function returns a list, so you can do:
case receive_messages(100) do
[] -> :ok
msgs -> do_stuff(msgs)
end
Now if you call Enum.each/2 or something like that on your list of messages you do not care wether the list is empty or not.







