Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should agents only hold immutable values

In Clojure Programming (OReilly) there is an example where both a java.io.BufferedWriter, and a java.io.Printwriter is put inside an agent (one agent for each). These are then written to inside the agent action. The book says that it is safe to perform io in an agent action. As I understand it all side effecting operations are ok inside an agent action. This is because agent actions inside commits are only run if the commit is succesful. And agent actions inside other agent actions are only run after the outer agent action completes successfully. Agent actions in general are guaranteed to be applied serially.

The Clojure documentation says this: "The state of an Agent should be itself immutable...".

As I understand it, the reason that atoms and refs must hold immutable values is so that clojure can roll back and retry commits several times.

What I don't understand is:

1: If Clojure makes sure that agent actions are only run once, why must agent values be immutable. (for example if I hold a java array in an agent, and add to it in an agent action, this should be fine because the action will only run once. This is very similar to adding lines to a BufferedWriter)

2: Is java.io.BufferedWriter considered immutable? I understand that you could have a stable reference to one, but if the agent action is performing io on it, should it still be considered immutable?

3: If BufferedWriter is considered immutable, how do I decide if other similar java classes are immutable?

like image 722
snowape Avatar asked Jun 07 '13 10:06

snowape


People also ask

When should I use immutable objects?

Immutable objects are particularly useful in concurrent applications. Since they cannot change state, they cannot be corrupted by thread interference or observed in an inconsistent state.

Why would you want an immutable object?

Thread safety Immutable objects can be useful in multi-threaded applications. Multiple threads can act on data represented by immutable objects without concern of the data being changed by other threads. Immutable objects are therefore considered more thread-safe than mutable objects.

Why is immutable data better?

Immutable data structure and pure function lead to referential transparency, making it a lot easier to reason about the behaviour of your program. You also get backtracking for free when using functional data structure.

What is immutability and why it is good?

Immutability is the idea that data or objects should not be modified after they are created. This concept has permeated throughout computing, and it is a core tenant of functional programming languages such as Haskell, Erlang, and Clojure.


2 Answers

As I see it:

Values held by agents should be 'effectively immutable' (term borrowed from JCIP), in that they should always be conceptually equal to themselves.

This means, if I .clone() an object and compare both copies, original.equals(copy) should be true, no matter of what I do (and when).

In this sense, an instance of the typical Employee class full of getters/setters can not be guaranteed to equal to itself, in face of mutability: equals() will be defined as a field-by-field comparison, so the test can fail.

A BufferedWriter though, does not represent a value - its equality is defined in terms of being exactly the same object in memory. So it has a 'sound' mutability -unlike Employee's- which makes it apt for wrapping it in an agent.

I believe that you are right in that from the STM point of view, agent-value mutability wouldn't hurt a lot. But it would in that it'd break Clojure's time model, in which you 'cannot change the past', etc.

On deciding whether a Java class is immutable: impossible without diving into the implementation. You don't have to care about this too much though.

I'd make the following taxonomy of types in Java-land:

  • Mutable objects which (badly) represent values - Employee, etc. Never wrap them in a Clojure reference type.

  • Immutable objects which represent values - their immutability is reflected in the doc, or in naming conventions ("EmployeeBuilder"). Safe to wrap in any Clojure reference.

  • Unmanaged collection types - ArrayList, etc. Avoid except for interop purposes.

  • Managed reference/collection types - AtomicReference, blocking queues... They play fine with Clojure, dubious to wrap them in Clojure references though.

  • 'IO' types - BufferedWriter, Swing stuff... you don't care about their mutability because they don't represent values at all - you just want them for their side effects. It might make sense to guard them in agents to guarantee access serialization.

like image 81
deprecated Avatar answered Sep 30 '22 04:09

deprecated


The agent value should be immutable because someone can do this:

 (def my-agent (agent (BufferedWriter.)))
 (.write @my-agent "Hello world")

Which is basically modifying the agent value (in this case the writer) without going through agent control mechanism.

Yes, BufferedWriter is mutable because by writing to it you can change its internal state. It is like a pointer or reference and not a value.

like image 20
Ankur Avatar answered Sep 30 '22 05:09

Ankur