Scope variables

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