I really like @peerreynders’ response … quality stuff
In addition to what they wrote, processes behave like objects and can be viewed as such. In fact, they are closer to what objects in early OO languages were envisioned to be than what we typically have today in OO languages: they encapsulate state and pass messages.
Where things diverge, however, is that OO tightly couples the code (as defined in i.e. a class) and the runtime instantiation (the object): the code defines the object. As a result, OOD revolves around sculpting objects that map to a problem space which leads directly to how the code is arranged. That has deep design implications, which can be good or … less … good, largely depending (at least IME) on the domain that is being modeled. (Also the care given and skill of the people doing the design and implementation, but let’s assume we’re all reasonably competent and caring coders here … )
With Elixir’s processes (which are almost-but-not-perfectly Actors, at least academically), those two concerns are separated. The code should be distributed into modules along topics (however you end up defining those) to keep like actions on like concerns together. But then the “objects”, those processes, can run any mix of that code as they wish.
So with Elixir you really have two stages of design thought: the modules that provide the functionality, and (separately!) how those will be combined at run time to achieve different tasks. The former is “static” (written, compiled, runnable) and the latter is “dynamic” (can be entirely non-deterministic in terms of how many processes are created, when they are created, and what code paths they run).
So while in OO you sort of have one design phase where you try to model both the structure of your code and the expected runtime profile of it, with Elixir you think about these things separately.
Which is why, while Elixir processes are pretty much objects, they don’t direct design as the do in an OO language. This in turn is why we shouldn’t try to distribute the code in our Elixir applications in terms of processes (or: “design in a classical OO way with processes being the objects”).
For OO languages, it’s “nice” in that you can see a direct and obvious mapping between the code in a class and the runtime shape of that same thing. They are coupled. This makes it simpler to reason about. But also ties ones’ hands. And this is where mixins and multiple inheritance start popping up to help paper over the pitfalls.
So while in Elixir I usually go through a “two-phase” design process, one thinking about the modules of code and one about the runtime properties (processes) that leverage that code, I am free to do what is best for each without compromise. A downside is that there is no mapping staring at you in the face from your modules of code as to what the runtime shape of things will be.
Currently Elixir requires us to build a separate, and largely implicit, mental model of the runtime shape of our application. It has occurred to me on a few occasions that it would be nice to have a more explicit set of language features for the definition and management of processes, syntatic sugar over the usual mix of supervisors, gen servers, spawn/spawn_link/spawn_monitor, process pools, registrations, etc. but which may grant us an “eagle eye’s view” of the runtime shape of our applications.
At one point I even drafted a small spec for what such a process definition DSL might look like … I think it would doable and useful, though I doubt it would be able to entirely replace the current APIs we use in all situations … but often 95% is better than 0% in these cases.