Different closure between Erlang and Elixir

Hello everyone,

Here is some erlang code I wanted to translate to Elixir… here is a simple example in the console.

$ erl
Erlang/OTP 22 [erts-10.4.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Eshell V10.4.4  (abort with ^G)
1> Pid=self().                            
<0.82.0>
2> Ref = monitor(process, Pid).              
#Ref<0.2147538226.2721841158.105393>
3> receive                                
3> {'DOWN', Ref, process, Pid, Why} -> Why
3> end.

and here is the Elixir version I came up with…

$ iex
Erlang/OTP 22 [erts-10.4.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Interactive Elixir (1.9.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> pid = self()
#PID<0.108.0>
iex(2)> ref = Process.monitor(pid)
#Reference<0.417878796.1379926020.235519>
iex(3)> receive do
...(3)> {:DOWN, ref, :process, pid, why} -> why            
...(3)> end
warning: variable "pid" is unused (if the variable is not meant to be used, prefix it with an underscore)
  iex:4

warning: variable "ref" is unused (if the variable is not meant to be used, prefix it with an underscore)
  iex:4

Code is quite similar, but in the Elixir version, ref and pid are not seen by the receive block, while the Erlang version will match on a ‘DOWN’ message where Ref and Pid are set.

Why is that different?

Thanks for taking time

1 Like

Elixir allows to rebind the variables, so this is perfectly possible to do:

a = 1
a = 2

Which will then be translated to Erlang as a something like:

A@1 = 1
A@2 = 2

To have the same behaviour as in Erlang, you need to use pin operator (^):

a = 1
^a = 2 # will fail with no match

So in your case you need to write it as:

receive do
  {:DOWN, ^ref, :process, ^pid, why} -> why
end

To “pin down” the variables to their current binding instead of creating new ones.

7 Likes

Thanks, I forgot the pin :slight_smile:

3 Likes

Erlang doesn’t distinguish between binding a value and pattern-matching on an already-bound value (part of its Prolog heritage IIRC). Elixir disambiguates between these two cases with the ^ operator, referred to as the “pin operator”:

https://hexdocs.pm/elixir/Kernel.SpecialForms.html#^/1

So this (from your example) binds new values to ref and pid:

receive do
  {:DOWN, ref, :process, pid, why} -> why
end

while this pins them to their already-bound values:

receive do
  {:DOWN, ^ref, :process, ^pid, why} -> why
end
4 Likes

Thanks, I need a :coffee: :slight_smile:

This is the full function

  def on_exit(pid, fun) do
    spawn(fn ->
      ref = Process.monitor(pid)
      receive do
        {:DOWN, ^ref, :process, ^pid, reason} -> fun.(reason)
      end
    end)
  end
2 Likes