Suponha que você tenha algumas contas bancárias:
(def accounts
[(ref 0)
(ref 10)
(ref 20)
(ref 30)])
E uma função atômica de "transferência":
(defn transfer [src-account dest-account amount]
(dosync
(alter dest-account + amount)
(alter src-account - amount)))
Que funciona da seguinte maneira:
(transfer (accounts 1) (accounts 0) 5)
(map deref accounts)
=> (5 5 20 30)
Você pode compor facilmente a função de transferência para criar uma transação de nível superior, por exemplo, a transferência de várias contas:
(defn transfer-from-all [src-accounts dest-account amount]
(dosync
(doseq [src src-accounts]
(transfer src dest-account amount))))
(transfer-from-all
[(accounts 0) (accounts 1) (accounts 2)]
(accounts 3)
5)
(map deref accounts)
=> (0 0 15 45)
Observe que todas as transferências múltiplas ocorreram em uma única transação combinada, ou seja, foi possível "compor" as transações menores.
Fazer isso com bloqueios seria complicado muito rapidamente: supondo que as contas precisassem ser bloqueadas individualmente, seria necessário estabelecer um protocolo na ordem de aquisição de bloqueios para evitar conflitos. É muito fácil cometer um erro difícil de detectar. O STM te salva de toda essa dor.