Il n'y a rien de mal à ce qu'un langage fonctionnel maintienne un état mutable. Même les langages fonctionnels "purs" tels que Haskell doivent maintenir leur état pour interagir avec le monde réel. Les langages fonctionnels "impurs" comme Clojure permettent des effets secondaires qui peuvent inclure l'état de mutation.
Le point principal est que les langages fonctionnels découragent l'état mutable, sauf si vous en avez vraiment besoin . Le style général est de programmer en utilisant des fonctions pures et des données immuables, et d'interagir uniquement avec l'état mutable "impur" dans les parties spécifiques de votre code qui en ont besoin. De cette façon, vous pouvez garder le reste de votre base de code "pur".
Je pense qu'il y a plusieurs raisons pour lesquelles la STM est plus courante dans les langages fonctionnels:
- Recherche : la STM est un sujet de recherche brûlant, et les chercheurs en langage de programmation préfèrent souvent travailler avec des langages fonctionnels (un sujet de recherche en soi, plus il est plus facile de créer des "preuves" sur le comportement du programme)
- Le verrouillage ne compose pas : STM peut être considéré comme une alternative aux approches de concurrence basées sur le verrouillage, qui commencent à rencontrer des problèmes lorsque vous évoluez vers des systèmes complexes en composant différents composants. C'est sans doute la principale raison "pragmatique" de la STM
- STM correspond bien à l'immuabilité : si vous avez une grande structure immuable, vous voulez vous assurer qu'elle reste immuable, donc vous ne voulez pas qu'un autre thread entre et mute un sous-élément. De même, si vous pouvez garantir l'immuabilité de ladite structure de données, vous pouvez considérer de manière fiable qu'il s'agit d'une "valeur" stable dans votre système STM.
Personnellement, j'aime l'approche de Clojure consistant à autoriser la mutabilité, mais uniquement dans le contexte de «références gérées» strictement contrôlées qui peuvent participer aux transactions STM. Tout le reste dans la langue est "purement fonctionnel".
;; define two accounts as managed references
(def account-a (ref 100))
(def account-b (ref 100))
;; define a transactional "transfer" function
(defn transfer [ref-1 ref-2 amount]
(dosync
(if (>= @ref-1 amount)
(do
(alter ref-1 - amount)
(alter ref-2 + amount))
(throw (Error. "Insufficient balance!")))))
;; make a stranfer
(transfer account-a account-b 75)
;; inspect the accounts
@account-a
=> 25
@account-b
=> 175
Notez que le code ci-dessus est entièrement transactionnel et atomique - un observateur externe lisant les deux soldes dans une autre transaction verra toujours un état atomique cohérent, c'est-à-dire que les deux soldes totaliseront toujours 200. Avec la concurrence basée sur les verrous, c'est un problème étonnamment difficile à résoudre dans un grand système complexe avec de nombreuses entités transactionnelles.
Pour un éclaircissement supplémentaire, Rich Hickey fait un excellent travail pour expliquer la STM de Clojure dans cette vidéo