An agent is a background process that maintains state. This state can be accessed at different places within a process or node, or across multiple nodes.
The initial state is set by a function we pass in when we start the agent.
We can interrogate the state using Agent.get, passing it the agent descriptor and a function. The agent runs the function on its current state and returns the result.
We can also use Agent.update to change the state held by an agent. As with the get operator, we pass in a function. Unlike with get, the function’s result becomes the new state.
Here’s a bare-bones example. We start an agent whose state is the integer 0. We then use the identity function, &(&1), to return that state. Calling Agent.update with &(&1+1) increments the state, as verified by a subsequent get.
| iex> { :ok, count } = Agent.start(fn -> 0 end) |
| {:ok, #PID<0.69.0>} |
| iex> Agent.get(count, &(&1)) |
| 0 |
| iex> Agent.update(count, &(&1+1)) |
| :ok |
| iex> Agent.update(count, &(&1+1)) |
| :ok |
| iex> Agent.get(count, &(&1)) |
| 2 |
In the previous example, the variable count holds the agent process’s PID. We can also give agents a local or global name and access them using this name. In this case we exploit the fact that an uppercase bareword in Elixir is converted into an atom with the prefix Elixir., so when we say Sum it is actually the atom :Elixir.Sum.
| iex> Agent.start(fn -> 1 end, name: Sum) |
| {:ok, #PID<0.78.0>} |
| iex> Agent.get(Sum, &(&1)) |
| 1 |
| iex> Agent.update(Sum, &(&1+99)) |
| :ok |
| iex> Agent.get(Sum, &(&1)) |
| 100 |
The following example shows a more typical use. The Frequency module maintains a list of word/frequency pairs in a map. The dictionary itself is stored in an agent, which is named after the module.
This is all initialized with the start_link function, which, presumably, is invoked during application initialization.
| defmodule Frequency do |
| |
| def start_link do |
| Agent.start_link(fn -> %{} end, name: __MODULE__) |
| end |
| |
| def add_word(word) do |
| Agent.update(__MODULE__, |
| fn map -> |
| Map.update(map, word, 1, &(&1+1)) |
| end) |
| end |
| |
| def count_for(word) do |
| Agent.get(__MODULE__, fn map -> map[word] end) |
| end |
| def words do |
| Agent.get(__MODULE__, fn map -> Map.keys(map) end) |
| end |
| |
| end |
We can play with this code in IEx.
| iex> c "agent_dict.exs" |
| [Frequency] |
| iex> Frequency.start_link |
| {:ok, #PID<0.101.0>} |
| iex> Frequency.add_word "dave" |
| :ok |
| iex> Frequency.words |
| ["dave"] |
| iex(41)> Frequency.add_word "was" |
| :ok |
| iex> Frequency.add_word "here" |
| :ok |
| iex> Frequency.add_word "he" |
| :ok |
| iex> Frequency.add_word "was" |
| :ok |
| iex> Frequency.words |
| ["he", "dave", "was", "here"] |
| iex> Frequency.count_for("dave") |
| 1 |
| iex> Frequency.count_for("was") |
| 2 |
In a way, you can look at our Frequency module as the implementation part of a gen_server—using agents has simply abstracted away all the housekeeping we had to do.