But here is the thing: it seems to have so many concurrency abstractions (via OTP) that I am often puzzled about which one I should use in a given situation.
Should I use a GenServer? An Agent? A supervised Task? A Process? Or just spawn/1 a lambda? Some of these abstractions seem so close to one another.
I know I would probably find out if I read whole books on Elixir, but I'm surprised I couldn't find something that summarized all this simply.
Any resource you found useful?
Edit: I know that an Agent is built on top of GenServer, but it doesn't tell me when to use which.
1. read the doc. This is entirely covered, see below
2. Avoid using process which stand as a low level API.
3. Only use one of the Processes abstraction if you need a mutable state
4. Agent is the base abstraction on top of the process
5. use GenServer if you need more than a simple mutable state storage
6. more importantly: do not forget code organization is done by modules and functions, processes are not necessary for that purpose.
[1] Processes: https://elixir-lang.org/getting-started/processes.html
[2] Agent: https://elixir-lang.org/getting-started/mix-otp/agent.html
[3] GenServer: https://elixir-lang.org/getting-started/mix-otp/genserver.ht...
[4] distinction between Agent, Task and GenServer: https://elixir-lang.org/getting-started/mix-otp/agent.html#t...
[5] Superviser: https://elixir-lang.org/getting-started/mix-otp/supervisor-a...
OTP & Elixir’s concurrency / parallelism features
https://docs.google.com/presentation/d/e/2PACX-1vQSohWc-igWG...
But to answer a few for you, because I love the expressiveness I get from elixir and understand the confusion a lot of people have getting their heads in the right space of understanding it:
Think of agent as a specialized subset of a genserver. It holds a value, and let’s other processes ask for it, and it can be updated. Past that it doesn’t do much.
Everything is a process.
Supervised tasks are used for spawning off work into a new process, and keeping a connection to them so you know if they crash.
My advice generally would be to understand a little of how the low level processes communicate with send/receive, and then after that focus on getting your head wrapped around how those primitives are used to build that behavior. Most everything else is as implication of or an extension of that behavior.
If you can get that, and pair it with a good understanding of the actor model, you’re on a good path.