Is node/0 a pure function?

Thinking about Erlang functions in Haskell types

elixir erlang pure functional net_kernel

I had a very interesting debate with a co-worker today.

I'd pointed out that the following filter expression felt like it was doing unnecessary IO, in the Haskell sense:

Enum.filter(fn {pid, _} -> :erlang.node(pid) == node() end)

The argument was simply that node/0 was not a pure function, and using such a function as a predicate to Enum.filter/2 felt somewhat inelegant. It was an off-hand comment, and I didn't think much about it, until the author of the code in question challenged me on what it means for a function to be pure.

The gist of the argument was: since node() always returns the same thing throughout the run of a program on a given instance of the BEAM, is that not the same as purity, for all practical purposes?

All my theoretical arguments (like how running the code on different machines yields different results) seemed unconvincing to the colleague. His argument about node() being effectively a global constant in the context of the VM seemed to suffice for him to treat it like a pure function.

When theory fails (or the theorizer is not convincing enough), practice comes to the rescue. Here is a code snippet which proves that node() is not pure, because it implicity depends on the state of the net_kernel application:

erl -eval "
{ok, _} = net_kernel:start(['hello@kitty', shortnames]),
erlang:display(node()),
net_kernel:stop(),
{ok, _} = net_kernel:start(['bye@kitty', shortnames]),
erlang:display(node()).
" -s erlang halt

This will output two different values for node():

Eshell V8.3.5.3  (abort with ^G)
1> 'hello@kitty'
'bye@kitty'

This demonstrates that node/0 is not a pure function, even in the loose-but-practical sense of always returning the same value during one run of a program.

For comparison, the somewhat-analogous function in Haskell does indeed have the type IO Hostname.