Stateless in Seattle: How Elixir Creates State

Stateless in Seattle: How Elixir Creates StateJohn KohlerBlockedUnblockFollowFollowingApr 27Data is ImmutableData structures in Elixir are immutable.

Once I define a value, it cannot be changed.

When I declare the list of outer planets asouter_planets = ["Jupiter", "Saturn", "Neptune", "Pluto"]There’s not a damn thing I, nor Neil Degrasse Tyson could do to remove “Pluto” from that list.

Sure, I could try to change it, but all attempts will create a new list, leaving the old one fully intact.

iex> outer_planets = ["Jupiter", "Saturn", "Neptune", "Pluto"]`, iex> updated_planets = List.

delete(outer_planets, "Pluto")iex> IO.

inspect(updated_planets)iex> ["Jupiter", "Saturn", "Neptune"]iex> IO.

inspect(outer_planets)iex> ["Jupiter", "Saturn", "Neptune", "Pluto"]Although nothing stops me from simply re-assigning the variable to a different, non-mutable list to reflect my preference for the Greek Gods.

outer_planets = ["Zeus", "Cronus", "Poseidon", "Hades"]This is a detail that tripped me up a bit when I was first starting in Elixir.

I would consider functional languages like Haskell or ML, where once an identifier was assigned, there was no going back.

At first glance, this looks a hell of a lot like a mutation.

The key is that the actual list is not changing at all.

The list of the planets with Roman names (Jupiter, Saturn, Neptune, Pluto) still exists unchanged, we just don’t have a name for it anymore.

The list did not change, we created an entirely new list (Zeus, Cronus, …) and decided to call it outer_planets instead of the list of Roman gods.

So cool, Elixir has immutable data.

This is great for a whole bunch of reasons.

None of which I’ll get into now.

Elixir is not StatelessSure, you can’t mutate data, but it’d be unfair to say Elixir is entirely stateless.

A language needs to interact with the outside world, otherwise, programs would just make your computer hot.

Lets imagine we wanted to build a very simple server.

All this server would do is keep count.

We could tell the server to increment the count, or we could request the current count.

Easy.

Lets do this by writing a recursive function called count:def count(message, count) do cond do message == :increment -> count(message, count + 1) message == :get -> count endendUh oh, this doesn’t really work.

Calling count(:increment, 0) will loop forever, and calling count(:get, 0) will just return 0.

We don't have a way to get the state of count out of the function.

To do this, we'll have to take advantage of Elixir processes.

Introducing — Elixir ProcessesElixir processes are somewhat similar to system processes.

They run on the erlang VM and are incredibly lightweight.

The Elixir docs boast that even modest systems can run hundreds of thousands of these processes concurrently without breaking a sweat.

Processes have two important properties that we’ll need for our stateful count server.

First, a new process can be created by using the spawn function.

process_id = spawn(fn -> 1 + 1 end)spawn accepts a function, and will return the process id of a new process.

This new process performs the given function and exits.

We'll need this process id later on.

Processes also have the ability to send and receive messages to/from each other.

They essentially have a ‘mailbox’, which we will be spamming with all our state, keeping our code immutable and clean.

A process can send a message to a given process id, or it can call receive to look at the first message in it's mailbox.

Calling receive when no messages are in the mailbox will cause the process to wait until a message is found (of course timeouts and configurations can be made, this is just the default behavior).

Here's an example:def print_message() do receive do message -> IO.

inspect(message) end end process_id = spawn(print_message)send(process_id, "Some message")Lets break down what happened here.

We define a function that will wait to receive a message in it’s mailbox and then print it out.

receive has a body similar to case, the clause matching the received message is computed.

We spawn a process that runs this function.

The process will immediately get to the receive do, and then wait until it receives any message.

We send a message to that process.

The message identifier will match, and the message get's printed.

Now that we have all these pieces in place, we can finally finish our counting server, sell it to Google and become millionaires.

Lets use our recursive count function from before, but this time we will use process message passing to send and receive information regarding the illusive count.

def count(current_count) do receive do :increment -> count(current_count + 1) {:get, pid} -> send(pid, current_count) count(current_count) endendprocess_id = spawn(fn -> count(0) end)send(process_id, :increment)send(process_id, :increment)send(process_id, :increment)send(process_id, :increment)send(process_id, {:get, self()}) # self is the current process idWe’ve built a process that runs an infinitely looping recursive function, which responds to our requests to alter or receive data.

But when we run the above code, we don’t get an answer back.

Did our response get lost in the mail?Well no, we just never checked our mailbox.

We’ll have to call receive in the current process to actually look at the response.

receive do response -> IO.

puts(response)endThere you go!.We built a stateful server using immutable data.

Of course, this would be a huge pain in the ass to implement every time we needed to utilize state.

Luckily, Elixir provides wrappers around these interfaces in the form of Agents and GenServers, which we will explore in later posts.

Originally published at https://codingwithalchemy.

com on April 27, 2019.

.

. More details

Leave a Reply