Woody88
Process and Stateness are they object?
Hey everyone,
So I have been reading two elixir books Functional Web Development with Elixir, OTP, and Phoenix and Elixir in action.
In the first book , it shows you how to hold state using Agents then later on changes the agents to GenServers. Now, I was starting to really understand what they were trying to convey, and the idea that kinda came up was that processes can be use kind of like objects to save your state. Now in Elixir in action, I don’t remember if it was the in book or in one of @sasajuric’s blog (Sorry I have been so busy and I have been reading a lot of other books as well so my mind is kinda of all of the place), but I believe that he mentions that we should avoid using processes has a mean to create objects in elixir.
Can someone please clarify and elaborated on this ? Because now I feel like I am abusing processes.. It’s as if I am going back in that database model form. I’m finally getting the part where your phoenix is not your app, I try to tackle things by creating my system separately with elixir first to move away from creating my module around DB model. But now I am confused again.
I feel like I am missing more things so you guys can better understand, but I don’t have much time and I really wanted to post this now because its been bugging my mind for a while now. I will try to add what I forgot later on if any.
Thanks in advance.
Most Liked
JEG2
Functional Web Development with Elixir, OTP, and Phoenix is currently being revised towards a more idiomatic usage of Elixir processes. I would say that the design @sasajuric shows in Elixir in Action and blog posts like To spawn or not to spawn? is more what you should be paying attention to at this time.
Processes are not Elixir’s equivalent of objects from OO languages. You should model your data as some combination of the provided primitives and model behavior as functions that operate on those data structures. Processes are for isolating subsystems for reasons of performance and error handling.
peerreynders
Old quote:
- Ten Pounds in a Five Pound Sack - Representation Is the Essence of Programming
… Show me your flowcharts and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won’t usually need your flowcharts, they’ll be obvious.
Frederick Brooks - Mythical Man Month (1975)
Even today a lot Frederick Brook’s insights still apply - but when it comes to this particular point I personally believe that it has become obsolete. By focusing primarily on representation you’ll always end up with a CRUD system.
These days I tend to side with Sam Newman:
These capabilities may require the interchange of information — shared models — but I have seen too often that thinking about data leads to anemic, CRUD-based (create, read, update, delete) services. So ask first “What does this context do?”, and then “So what data does it need to do that?”
Sam Newman, Building Microservices (2015)
Ultimately the application is supposed support use-cases that will enable the users to meet some set of concrete goals and it’s the use-cases that help identify what data is necessary to achieve these goals - so really what the application is able to do should be the primary concern, the data that enables the “doing” is a necessary but secondary concern. So it makes sense to create some of the logical boundaries around the system’s activities (runtime berhavior) rather than the just focusing on neatly partitioning the data for persistent storage.
Actors and objects have one thing in common - they need to collaborate. However the means of that collaboration is quite different - message passing vs. method invocation. The motivation behind the encapsulation of state are quite different as well.
An actor doesn’t share state so it can maintain run-time autonomy and isolation (a byproduct of which is that there is no need for synchronization) - if it dies it doesn’t take the “rest of the world” with it. OOD heuristics on the other hand focus primarily on design-time autonomy and isolation. OO is more focused on segregating commonalities from variabilities and hiding implementation details that may be subject to change during the entire lifecycle of the application. When OO was conceived computers were still mostly uniprocessors and OO never really concerned itself with runtime isolation as fault tolerance and distributed processing were never stated design goals.
Both actors and objects are essentially “black boxes” that expose an (message or method-based) interface but the criteria that lead to their “optimal boundaries” are quite different. Now that doesn’t mean that the essence behind some of the OO design principles can’t, after some careful re-interpretation, apply to actor-based systems - but thats a separate topic.
To spawn, or not to spawn? is quite specific:
- Use functions and modules to separate thought concerns.
- Use processes to separate runtime concerns.
- Do not use processes (not even agents) to separate thought concerns.
Now initially I would have been tempted to use “design concerns” instead of “thought concerns” - but that would have been a grave mistake because in Erlang/Elixir “runtime concerns” are also “design concerns” - less so because of performance but moreso because of fault-tolerance. So my interpretation of “thought concerns” is the typical compartmentalization of functionality that we engage in to make things “easier to reason about” while simultaneously structuring the application to align itself with the user’s domain. So deck, hand, round align with the domain concepts while round_server and notifier are primarily concerned with orchestrating how the rounds unfold over time.
Now it would be a mistake to try to compare the “size” of processes and objects - some processes can be large, i.e. hold complex state that would typically be divided over a large number of objects but there are also processes that are extremely short-lived and trivial (e.g. completing a (blocking) “call” for the parent process which can’t afford to be blocked).
OO design often obsesses with reuse - ignoring YAGNI in the process. Lately a new criterion for choosing boundaries seems to have emerged - impermanence and replaceability. Actually, it’s not all that new:
Program to an interface, not an implementation.
p.18 Design Patterns: Elements of Reusable Object-Oriented Software (1994)
Now typically this is seen as a means to break the coupling to a concrete implementation but it also has the effect that you can “throw away” the current implementation and replace it with an entirely new (rewritten) one.
So recently some teams have been placing more emphasis on composing systems from parts which are “small enough” to be rewritten in one day to two weeks (depending on the circumstances). If any one part gets “large enough” to become “too large or difficult to replace” it is refactored towards a smaller implementation or broken down into an arrangement of more replaceable parts. So “replaceability” could be another aspect that can potentially help to find better boundaries for something as small as a process or something a large as an OTP application.
aseigo
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.
Last Post!
aseigo
At the end of the day, those are actually the same things with Elixir. In the process there is always a function that is currently executing (possibly waiting on a message ..), and the state is indeed handed from one function to the next in a state-holding process.
The reason the state is not visible from the “outside” is that the state is held in a function waiting on messages to arrive … when a message arrives (without the state, of course), the receiving function now has the “next thing to do” from the message and the state that it received when it started.
This is why GenServer callbacks (for instance) all take and return a state parameter! So processes are a nice way to wrap those functions-bearing-state in an easy to use package.
So now this:
SQL databases, ets and mnesia are about “durable” storage → storage that exists outside of the rest of the program and may live a different life cycle .. (ok, there are details in there i am glossing over, but let’s roll with that simplification for now).
Data in processes are just parameters in functions (representing state) that are waiting to be used when a message is received.
Yes, they are separate entities .. but you can view them all as being “things we wrap in functions”. So for a SQL database we write Ecto queries (or maybe go straight to postgrex if we’re feeling like that
) inside of functions and write other functions that know how to manipulate those queries and their results (perhaps with structs). Those functions create the surface with which you call out into these “separate systems”, and when using those functions your program can generally ignore the “detail” that they are separate from your program.
I think I may understand where you feel the flow is dropping out from under your feet: with OO there is this make-believe world of data being managed in very solid things (called “objects” even!), but it’s really just functions wrapped behind functions with syntactic sugar that keeps the data<->functions association for you (in that sense, Elixir processes are analogous to objects); with structural languages like C, there are globals and the endless passing of pointers .. but that also is just data being handled by functions.
There is no extra magic, or lack thereof, in functional programming in that regard. It just doesn’t try to hide the fact that data and functions are loosely coupled, if at all .. it goes so far as to tell us that functions are also just data, which is obvious when you think about the fact that they are just bits in memory that the computer reads through to know what instructions to execute .. bits are bits, right?
When I came to functional programming, I also felt a bit of vertigo over where was my data going to find its way through the program … all the facades I was used to relying on as mental abstractions for the reality were gone. But then you learn not to worry about it .. it’s just data, some of which are executable functions ![]()
.. and that’s true in EVERY language .. they just have different ways of exposing it, and different rules for interacting with the data that is your program.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










