Hi
The following code is part of a module that has a main function that spins two processes and sends to each of the processes an argument. The processes perform an operation and return a value back to the main process.
The main process shall multiply the received values from the processes with each other and print to screen.
def run_receive() do
receive do
{sender, msg} ->
if sender == "p1_p" do
a1 = msg
else
a2 = msg
end
run_receive()
after
1000 ->
IO.puts(a1 * a2)
IO.puts("Nothing else received. Terminating....")
end
end
I am getting the following errors
warning: variable "a2" is unused (if the variable is not meant to be used, prefix it with an underscore)
temp.exs:20: TwoProcessesMulti.run_receive/0
warning: variable "a1" is unused (if the variable is not meant to be used, prefix it with an underscore)
temp.exs:18: TwoProcessesMulti.run_receive/0
warning: variable "a1" does not exist and is being expanded to "a1()", please use parentheses to remove the ambiguity or change the variable name
temp.exs:26: TwoProcessesMulti.run_receive/0
warning: variable "a2" does not exist and is being expanded to "a2()", please use parentheses to remove the ambiguity or change the variable name
temp.exs:26: TwoProcessesMulti.run_receive/0
Which I am guessing has to do with scope variables. what would be your approach to such task?
Thanks
There are two distinct scoping issues with this code:
warning: variable "a1" is unused (if the variable is not meant to be used, prefix it with an underscore)
warning: variable "a2" is unused (if the variable is not meant to be used, prefix it with an underscore)
These arise from the if
expression; variables bound inside one of the clauses of the if
are only accessible within that clause.
warning: variable "a1" does not exist and is being expanded to "a1()", please use parentheses to remove the ambiguity or change the variable name
warning: variable "a2" does not exist and is being expanded to "a2()", please use parentheses to remove the ambiguity or change the variable name
On the other hand, the code in the after
does not have access to variables bound in the ->
part of receive
.
In the function provided, the only way to supply variables to the after
is by passing them as arguments. That’s also where msg
can be stored:
def run_receive(a1, a2) do
receive do
{sender, msg} ->
if sender == "p1_p" do
run_receive(msg, a2)
else
run_receive(a1, msg)
end
after
1000 ->
IO.puts(a1 * a2)
IO.puts("Nothing else received. Terminating....")
end
end
This loop would be started with run_receive(nil, nil)
.
1 Like
Thanks buddy.
One more question then. what would be the typical way of implementing such task? Is the idea here that we always try and perform operations in individual functions? e.g for the above example, would you pass the a1 and a2 to a function called run_calculate and let that function do the multiplication instead of having the run_receive doing it?
Thanks once again
The after
block only matters if your receive
times out (see receive/1), so if you bind to anything in receive
the after
doesn’t matter.
This sounds like a great use case for the Task module. Your main function could just create a couple tasks then wait for them.
task_a = Task.async(fn -> do_task_a(some, args) end)
task_b = Task.async(fn -> do_task_b(other, args) end)
IO.puts(Task.await(task_a) * Task.await(task_b))
If you don’t want to switch to Task
and want to use the same processes, you can receive messages in any order you want, not just how they come in.
a = receive do {"p1_p", msg} -> msg end
b = receive do {"p2_p", msg} -> msg end
a * b
This is cool. You can assign a whole operation to a variable. Interesting.
Thanks for the input
You might extract a function like run_calculate
if the operations inside the after
clause were complicated, or if you wanted to test them independently from the receive
loop - but there’s no particular “rule” about when to do that.
One thing to consider is the exact behavior you’re looking for from the function. For example, the fun_receive
version behaves like this:
- retains the most recently received value for
a1
and a2
- prints their product after no updates to
a1
or a2
for a full second
On the other hand, the code in @brettbeatty’s post behaves differently:
- receive exactly one value for
a1
and a2
- prints their product immediately
Also consider what happens in each version when some of the expected messages don’t arrive.
For more discussion of the “recursive calls to maintain state” pattern, see the “State” section in the Elixir guide.
1 Like