When you map over the collection of elements and spawn a new process for each element in the collection, you'll get back a list of pids: these pids will be in the same order as the collection you're mapping over, i.e., the pid for a given elem
is in the same position in the list of pids as the elem
in the original collection. This is just how mapping works, you apply an operation to each element of a list and get back a list of the results of these operations.
Now, you map over the list of pids. When you match on ^pid
, the code will block until a message from the current pid
you're mapping arrives to the current process. However, the {^pid, result}
message may not be the only one or the first one in the message queue of the current process: since all the spawned processes are now running in parallel, they won't be sending the results in the order they've been spawned. This means that when you receive and match on {^pid, result}
, the message queue could have other messages ({pid_1, result_1}
, {pid_2, result_2}
) before the one that will match {^pid, result}
. Thanks to how receiving processes works in Erlang, these messages will just be skipped if they don't match the pattern in receive
, until one of them matches (or we keep waiting for new ones that match).
When you match on {pid, result}
, you're saying that any two-element tuple is fine: in this case, pid
will likely not be the pid
you're currently mapping exactly for the reasons I talked about above (spawned processes will send results back in a not predictable order).
A more visual representation: say you have this message queue in the current process, after the spawned processes have start running (we'll call the spawned processes pid1
, pid2
, and so on):
# The one on top is the first in the message queue:
{pid3, res3}
{pid1, res1}
{pid4, res4}
{pid5, res5}
{pid2, res2}
and let's say you're currently mapping pid1
(i.e., pid
in the function you pass to Enum.map/2
is pid1
).
When you do receive do {^pid, res} -> ...
(with pid == pid1
), the first message will not match; hence, the next message will be matched and this one will match. {pid3, res3}
will be put back in the message queue and the receive
will be executed with {pid1, res1}
. The message queue looks like this:
# The one on top is the first in the message queue:
{pid3, res3}
{pid4, res4}
{pid5, res5}
{pid2, res2}
Back to the original queue: now, if you match on {pid, res}
(without the ^
pin operator), any two-element tuple will match; specifically, {pid3, res3}
will match and the receive
block will be executed with that (even if the pid
we're mapping is pid1
). This means that in the resulting list, the result for the 3rd element (for which pid3
was spawned) is in the place of the result for the 1st element: random order!