cult3

Using Agents in Elixir

Aug 17, 2016

Table of contents:

  1. What are the common aspects of server processes?
  2. What are Agents?
  3. Refactoring to an Agent
  4. Conclusion

In last week’s tutorial we looked at storing state in Elixir using processes. In functional programming languages, in order to maintain state you need to continuously pass it between functions.

Processes allow us to store state in Elixir, possibly forever, by running an infinite loop of recursive function calls. We can then access this state via message passing. Anyone who knows the pid of the process can interact with the state via message passing. In many ways this is very similar to the original idea of Object-Oriented Programming.

As you can probably imagine, storing state in Elixir processes is really common. Instead of writing the boilerplate code that we wrote last week, Elixir provides a number of helpers to enable this functionality without you having to write it yourself.

One such example is the Agent module. In today’s tutorial we are going to be looking at using Agents in Elixir.

What are the common aspects of server processes?

Although we have already explored the common aspects of server processes in last week’s tutorial, let’s quickly reiterate the common characteristics. When I say “server process” I just mean a process that runs for a long time and holds state.

Here are the requirements of a server process:

  • a way to start the process
  • a function that can be called recursively in an infinite loop passing the state for each function call
  • a way to handle messages
  • a way to return responses to the caller

So instead of implementing this functionality in every server process, we can abstract this functionality and concentrate on the actual functionality of that particular server process instead.

This removes duplication and ensures that all server processes work in exactly the same way.

What are Agents?

An Agent is a simple wrapper around storing state in a process. In last week’s tutorial we wrote the functionality to store state inside of the process using an infinite loop.

But this is such a common thing to do in Elixir that it has been abstracted into something called an Agent.

Before we rewrite the example from last week to use an Agent, let’s take a quick look at using Agents in iex.

To start the process, you call the start_link function and pass a function containing the initial state:

{:ok, pid} = Agent.start_link(fn -> %{} end)

This is just like we saw in last week’s tutorial where we are passing an empty map as the initial state.

The return value from this function call is a tuple with an atom of :ok as the first element and the pid of the process that was created as the second element.

To update the value we can use the Agent.update/2 function that accepts the pid of the process as the first argument and a function as the second argument:

Agent.update(pid, &Map.put(&1, :hello, :world))

In this example I’m using the & operator to capture the Map.put/3 function and &1 to represent the first argument passed into the function. In this case the &1 is the initial state map that we passed in when we started the process.

Finally, to retrieve a value we can use the Agent.get/2 function, which again accepts the pid of the process as the first argument and a function as the second argument:

Agent.get(pid, &Map.get(&1, :hello))

If you run this code in iex you will see that we can store state in a process using an Agent without having to write the infinite loop ourselves.

Refactoring to an Agent

So now that we have looked into using Agents in iex, lets refactor the code from last week to use the Agent instead.

First up we can delete all of the functions that we wrote last week and add the start_link/0 function for kicking off the process:

defmodule StatefulMap do
  def start_link do
    Agent.start_link(fn -> %{} end)
  end
end

Next we can add the put/3 function for adding an item to the map:

def put(pid, key, value) do
  Agent.update(pid, &Map.put(&1, key, value))
end

And finally we can add the get/2 function for retrieving an item from the map:

def get(pid, key) do
  Agent.get(pid, &Map.get(&1, key))
end

Here is the module in full rewritten to use an Agent:

defmodule StatefulMap do
  def start_link do
    Agent.start_link(fn -> %{} end)
  end

  def put(pid, key, value) do
    Agent.update(pid, &Map.put(&1, key, value))
  end

  def get(pid, key) do
    Agent.get(pid, &Map.get(&1, key))
  end
end

And here is how you would use it in iex. First we start the process and get the pid:

{:ok, pid} = StatefulMap.start_link()

Next we can use the pid to add an item to the map:

StatefulMap.put(pid, :hello, :world)

And finally we can retrieve the value from the map using the pid and key we are interested in:

StatefulMap.get(pid, :hello)

Conclusion

Storing state in processes is a really common thing to do and so Elixir provides Agents to make this really simple. Instead of implementing the functionality yourself you can simple use the Agents.

Agents provide functions to add, update, read, and delete from the state and so you can use an Agent whenever you need to store and interact with state. Over the coming weeks you will see more practical examples of using Agents in Elixir code.

In the mean time, you can read more into the functionality of Agents by taking a look at the documentation.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.