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.
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.
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.
@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
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.
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.
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.
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.
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.
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.