The many states of Elixir

Underjord is a tiny, wholesome team doing Elixir consulting and contract work. If you like the writing you should really try the code. See our services for more information.

We of the blessed church of Functional Programming pride ourselves on our immutability, purity and lack of noxious side-effects. We do not mutate the state. We only produce new and better state. Deterministic state. Correct state. The best state.

Except that I work in Elixir. Elixir is built on Erlang. I like to think Erlang being FP is mostly an accidental side-effect of trying to solve a bunch of complex requirements around distributed systems, fault tolerance and consistently low latency (this predates the concurrency story, another kind-of-accident). Elixir and Erlang are decidedly loose in structure.

I get the sense that they appeal to some specific flavors of FP enthusiasts only because they are FP and have undeniable practical applications. Accepted due to being (relatively, practically) successful and interesting. In spite of not being quite strict, rigorous or firm enough to give a true hard FP lover the rigidity they truly desire. Moving on from that imagery. Suffice to say Elixir is not pure.

You can write parts of your application that are generally pure and that has great advantages. However the language and plattform also offers a ton of tools to work differently with regards to state and mutability.

There are wild side effects for logging, IO, processes, message passing and many other things. It is a very practical language. Often we also need mutable state.

Surely not!?

Yes. Quite.

ETS

I would say that this is the blessed path to mutability in Erlang-land. If you want to store something in memory and you have to be able to read and write to it kind of ad-hoc. ETS can help you out. It is a bit of swiss army knife all told and has a bunch of different types of tables (bags, duplicate bags, sets and probably more, I’m going from memory).

A common misstep in Elixir and Erlang is to implement an in-memory store as a GenServer. This can be the right move if serial access is important, if what you need is a bottle-neck where you can do something important. But in many cases what you would probably rather have is an ETS table and you might enable options for improved read concurrency or improved write concurrency.

ETS is a super useful tool. It is also very much a way to get mutable local or global state.

I will gloss over DETS because that’s IO (ETS on file) and I will skip Mnesia because it is a whole-ass database.

Persistent Term

Have some global data that rarely, but occasionally, changes? Persistent term. You get really great read performance. Updating or deleting a term gets really expensive.

A very pragmatic solution for an in-memory store.

Process dictionary

I guess this is the Erlang process equivalent of something being thread-local. It is a key value mechanism that lives and dies with your process. Actually we had Mat Trudel of Bandit fame on BEAM Radio just now so there will be an episode soon where he outs his dirty, filthy use of this local mutable data in his fabulous web server.

These are also used for dynamic repos in Ecto if I’m not mistaken which can be kind of surprising. I believe they are also used for the Ecto sandbox which means it can be kind of funny crossing process boundaries. Depends on your options and what you need.

Very impure, very practical. I find myself reaching for it when I need to be able to switch some slight behavior for testability or similar and don’t necessarily have a good way to pass config around. Crimes.

Application environment

Now we are getting worse. An Erlang application is something like a library or particular app project. Each app can store configuration and assorted dodgy bogus in their own ”environment”.

It is intended for reading but of course you can write to it. I couldn’t tell you what the performance characteristics are because the act is a crime and we shouldn’t benchmark crimes. If you do benchmark the crime, let me know.

I have done this in Livebook where it is a viable way of setting up configuration during the setup block. I have also done it in relation to particular tests at times.

System environment

Environment variables are great. But we usually treat them like constants. No more! You can trivially change environment variables and suddenly you have an awesome KV store that you should absolutely not use in this way. Rad.

Update!

Karl-Johan Nilsson immediately mentioned counters and atomics after I posted this. Shows what I know.

—-

I think that covers the mutable states I know of that makes Elixir and Erlang practically capable but ever so impure. If I have horribly misrepresented something or smeared your favorite FP language, no worries. You can complain at lars@underjord.io, berate me on Mastodon via @lawik@fosstodon.org or even like, and subscribe my latest video.

Underjord is a 4 people team doing Elixir consulting and contract work. If you like the writing you should really try the code. See our services for more information.

Note: Or try the videos on the YouTube channel.