Why does Process.flag look like an OOP type method?

My topic title is probably horrible, but here goes.

Process.flag(:trap_exit, true) sets the trap_exit flag for the current process correct? Isn’t this acting like an OOP method? I haven’t provided the function the pid of the process calling the function. I’m not 100% sure if I’m thinking about this the correct way or not, but it just looks odd.

3 Likes

probably because it’s the same way in erlang

1 Like

Yep. It’s well known you can pretty much only store state in processes or external systems. Most of the time that still feels pretty functional thanks to receive loops or genserver callbacks or other mechanisms.

Another way you can store state, local to a single process only, is using the process dictionary. People often discourage doing this precisely because it goes against the FP grain of the language, although it does make sense from time to time.

1 Like

So in calling Process.flag(:trap_exit, true) there’s an implicit this in there. Is that correct? This seems very anti-functional.

1 Like

Well you can’t change flags from other processes AFAIK, so requiring an explicit pid which has to match self() you would increase confusion even more.

3 Likes

That’s precisely it. There is also Process.flag/3 which takes the pid of the target, but that version accepts only those flags (actually just one) which can be set for another process.

Process.flag/2 is not the only such function. There’s also e.g. exit/1, and there are other such functions. I don’t think there’s anything implicit there. The docs for those function clearly state that they affect the calling process.

2 Likes

So in the case of exit:

@spec exit(term) :: no_return
def exit(reason) do
    :erlang.exit(reason)
end

We are calling a module function without passing our pid id.

1 Like

@sasajuric, where in the erlang/otp repo is the erlang module located? I’m trying to find the erlang code to see what’s going on but they seem to have this module pretty well hidden.

Please disregard. I found it:
otp/erts/preloaded/src/erlang.erl

1 Like

Most of its functions are implemented as BIFs, so you’ll need to dive a bit deeper. Here’s the source for exit/1 and process_flag/2.

2 Likes

@sasajuric I think I’m getting closer to the answer I’m looking for.

#define BIF_ALIST Process* A__p, Eterm* BIF__ARGS, BeamInstr *A__I

So these BIFs are being passed a reference to the process. I just haven’t found where the BIF process_flags is being called by the vm.

1 Like

It’s not clear to me what are you looking for?

Either way, I’m not super familiar with the VM code. AFAIK core parts are located in beam_emu.c and erl_process.c.

It appears that this part is where BIFs are invoked.

2 Likes

I’m just trying to figure out how Process.flag is getting the reference to the process since we aren’t passing one in.

1 Like

Well it can always call self/0 or its C counterpart to get the pid of the current process.

4 Likes

@Nobbz, there is the function Process.flag(pid, flag, value), although you can’t set all the same flags as you can with Process.flag/2.

1 Like

I understand that you can call self() all you want in your own functions, but so far in Elixir/Erlang we’ve been taught that the only way to change state is through sending messages. That does not appear to be the case with Process.flag/2. It appears to break our functional paradigm. Now if the explanation was that this is the only way the ERTS can access those flags of the Process then I get it. And, I am a true noob, so there may be a whole lot of other things like this that I haven’t encountered so y’all may be used to this.

1 Like

Think of it like an OS process, the kernel has access to it all and Process.flag is like a syscall to the kernel, it is the kernel that just mutates your underlying immutable state from the old state to the new state, and since it also handles all message routing it knows the new information associated with it as well.

The BEAM VM really does emulate a standard modern pre-emptive OS quite well for this.

1 Like

I may be going off on a tangent, but I’d like to expound upon this assumption, which I find over simplified. Processes are the “holders” of state. The logic within a process decides whether to change its state or not. Yes, the way to communicate with other processes and ask them to change their state is via messages, but the sending of the message really only adds to a process mailbox. It doesn’t directly make the state change – the receiving process does.

1 Like

That’s a good point, and I get that some of the built in functions are there because its the only way to do something. I get that in this case Process.link or Process.flag understands that the calling process is the one that needs modification. It’s just the first time I’ve looked at those functions and realized that it doesn’t fit how I thought we program stuff on the Erlang VM.

I would have thought that those functions would work something like

def flag(pid, :trap_exit, true) do
    case pid == self() do
        true ->
           set_trap_flag(pid, true)
        _ ->
           :error
    end
end

This is more declarative like the rest of the way we program.

1 Like

Couple things though, a process can only affect its own process state, plus you’d be calling self() multiple times needlessly (the caller and in the function to confirm), just to make the ‘syscall’ to trap the flag. Remember when you make a syscall in a real OS like Windows or Linux or so that most of them affect only the current process, without you needing to pass your own process id in.

1 Like

Functional paradigm refers only to a subset of Elixir (or Erlang for that matter). Whenever you send a message or exit signal to another process, or when you update something in an ETS table, or perform an I/O operation, there can be side-effects, and that’s definitely not functional. Most of non-functional examples are IMO somehow related to concurrency aspect of Elixir/Erlang, though there are also examples which are related to pure data transformations. The one that comes to mind is :rand.uniform/0,1 which uses process dictionary to manage the state of the RNG, and ensures that repeated calls will produce (possibly) different numbers.

The functional properties are mostly confined to data transformations in the shape of SomeModule.new |> SomeModule.transform_1(...) |> SomeModule.transform_2(...) |> .... In this case functions don’t produce side-effects, so you need to take their result to ensure their computations are not lost.

So, while Elixir is in some ways functional, in other ways (usually related to concurrent programming) it’s not.

1 Like