Agents (Scala)

Module stability: SOLID

The Agent class was strongly inspired by the Agent principle in Clojure. The section below is adapted from Clojure's documentation:

Agents provide independent, asynchronous change of individual locations. Agents are bound to a single storage location for their lifetime, and only allow mutation of that location (to a new state) to occur as a result of an action. Actions are functions (with, optionally, additional arguments) that are asynchronously applied to an Agent's state and whose return value becomes the Agent's new state. Because the set of functions is open, the set of actions supported by an Agent is also open, a sharp contrast to pattern matching message handling loops provided by Actors.

Agents are reactive, not autonomous - there is no imperative message loop and no blocking receive. The state of an Agent should be itself immutable (preferably an instance of one of Akka's persistent collections), and the state of an Agent is always immediately available for reading by any thread (using the '()' function) without any messages, i.e. observation does not require cooperation or coordination.

The actions of all Agents get interleaved amongst threads in a thread pool. At any point in time, at most one action for each Agent is being executed. Actions dispatched to an Agent from another single Agent or thread will occur in the order they were sent, potentially interleaved with actions dispatched to the same Agent from other sources.

If an Agent is used within an enclosing transaction, then it will participate in that transaction. See the section 'Transactional Agents' below for details.

Creating and stopping Agents

Agents are created by invoking 'Agent(..)' passing in the Agent's initial value.

val agent = Agent(5)

An Agent will be running until you invoke 'stop' on it. Then it will be eligible for garbage collection (unless you hold on to it in some way).


Updating Agents

Now you can change the Agent's (Agent[T]) value by either sending it a function of type 'f: (T => T)' or by sending it a value of type 'T'. The Agent will apply the new value or function atomically and asynchronously. The update is done in a fire-forget manner and you are guaranteed that it will be applied but not when. You apply a value and/or a function by invoking the 'send' function (called 'update' in release 0.7.x).

// send a value
agent send 7
// send a function
agent send (_ + 1)
agent send (_ * 2)

In 0.7.x the 'send' function is named 'update'.

Reading an Agent's value

Agents can be dereferenced, e.g. you can get an Agent's value, by invoking the Agent with parenthesis like this:

val result = agent()

You can also "read" an Agent's value by sending a procedure to it that will get the Agent's value as argument. Procedures have the type 'T => Unit', e.g. they don't return new value, which means that they will not affect the internal state of an Agent but are only used to "read" a value and do something with it. Procedures are sent using the 'sendProc' function and are sending the procedure to the Agent asynchronously.

Transactional Agents

If an Agent is used within an enclosing transaction, then it will participate in that transaction.

In more detail. If you do a 'agent send (_ * 2)' then this call returns immediately and the transaction continues in the thread that the Agent send is dispatched in. The client does not need to wait but can go off and do other transactional or non-transactional operations. If it does transactional ops then those will be part of the same transaction as the 'agent send' and this is atomic, e.g. either all fails or all succeeds. In general. If there are more than one Agent (and/or actor) in the transaction then they all need to wait on commit until all have committed.

Monadic usage

Agent is also monadic, which means that you can compose operations using for-comprehensions. In monadic usage the original Agents are not touched but new Agents are created. So the old values (Agents) are still available as-is. They are so-called 'persistent'.

Example of monadic usage:

val agent1 = Agent(3)
val agent2 = Agent(5)
// uses foreach
for (value <- agent1) {
  result = value + 1 
// uses map
val agent3 = 
  for (value <- agent1) yield value + 1 
// uses flatMap
val agent4 = for {
  value1 <- agent1
  value2 <- agent2
} yield value1 + value2 


You can *not* call 'agent.get', 'agent()' or use the monadic 'foreach', 'map and 'flatMap' within an enclosing transaction since that would block the transaction indefinitely. But all other operations are fine. The system will raise an error (e.g. *not* deadlock) if you try to do so, so as long as you test your application thoroughly you should be fine.
Home Turn Off "Getting Started"