|
|
|
|
|
by billsix
4916 days ago
|
|
At ILC 2009, Guy Steele asked Rich Hickey how Clojure's STM interacted with I/O. To which, Rich replied that it doesn't. No further discussion continued on the subject. I have found that this rarely enters discussions regarding Clojure, although the STM is frequently lauded.
GHC's STM also does not work with the IOMonad, which is clear because of the types, and is articulated in books such as such as Real World Haskell. http://book.realworldhaskell.org/read/software-transactional... Are people really getting benefits from transactions which do not involve I/O? |
|
The critical thing, as you observed, is to separate IO from retryable STM transactions with a barrier; e.g. by receiving data, doing an idempotent computation inside dosync or swap!, and then emitting results. Haskell has an advantage in that this separation is provable by the type system, whereas in Clojure you need to remember.
A contrived example:
where get-from-fridge and tell-friend are IO operations, and our list of eaten foods is mutated by pure functions.In practice I don't find this particularly limiting: dosync and swap! are almost always short operations for performance reasons anyway. Then you return a consistent snapshot of the updated state from dosync--where multiple pieces of state are involved, you can use vectors or maps along with destructuring bind. Sometimes it's a tad unwieldy, but typically much shorter than the equivalent mutex dance.
There are other aspects of Clojure's concurrency libraries, like agents, futures, and promises, which are useful in IO. Specifically, agents give you asynchronous serializability, futures give you asynchronous concurrency, and promises allow for synchronous handoff of delayed values. Those are quite useful when working with IO, though for heavy lifting you may be better off using explicit queues and worker pools from java.util.concurrent.
Sometimes I think of Clojure (as an imperative language) as the dual of Haskell's lazy evaluation model. Clojure uses futures, promises, and lazy sequences to provide explicit laziness, where Haskell uses the IO monad to provide explicit ordering. Both have an STM for serializable, atomic mutability between threads.