#+Title: Introduction à la Programmation Fonctionnelle avec Haskell #+Author: Yann Esposito #+Email: yann@esposito.host #+Date: <2018-03-15 Thu> #+LANGUAGE: fr #+LANG: fr #+HTML_HEAD: * Introduction à la Programmation Fonctionnelle avec Haskell *** main :: IO () #+BEGIN_SRC ████████████████████████████████████████████████████████████████████████████████████ █ █ █ Initialiser l'env de dev █ █ █ ████████████████████████████████████████████████████████████████████████████████████ #+END_SRC Install **stack**: #+BEGIN_SRC bash curl -sSL https://get.haskellstack.org/ | sh #+END_SRC Install **nix**: #+BEGIN_SRC bash curl https://nixos.org/nix/install | sh #+END_SRC ** Programmation Fonctionnelle? *** Von Neumann Architecture (1945) #+BEGIN_SRC +--------------------------------+ | +----------------------------+ | | | central processing unit | | | | +------------------------+ | | | | | Control Unit | | | +------+ | | +------------------------+ | | +--------+ |input +---> | +------------------------+ | +--> output | +------+ | | | Arithmetic/Logic Unit | | | +--------+ | | +------------------------+ | | | +-------+---^----------------+ | | | | | | +-------v---+----------------+ | | | Memory Unit | | | +----------------------------+ | +--------------------------------+ #+END_SRC made with http://asciiflow.com *** Von Neumann vs Church - programmer à partir de la machine (Von Neumann) + tire vers l'optimisation + mots de bits, caches, détails de bas niveau + actions séquentielles + *1 siècle d'expérience* . . . - programmer comme manipulation de symbole (Alonzo Church) + tire vers l'abstraction + plus proche des représentations mathématiques + ordre d'évaluation non imposé + *4000 ans d'expérience* *** Histoire - λ-Calculus, Alonzo Church & Rosser 1936 - Foundation, explicit side effect no implicit state . . . - LISP (McCarthy 1960) - Garbage collection, higher order functions, dynamic typing . . . - ML (1969-80) - Static typing, Algebraic Datatypes, Pattern matching . . . - Miranda (1986) → Haskell (1992‥) - Lazy evaluation, pure ** Pourquoi Haskell? *** Simplicité par l'abstraction *=/!\= SIMPLICITÉ ≠ FACILITÉ =/!\=* - mémoire (garbage collection) - ordre d'évaluation (non strict / lazy) - effets de bords (pur) - manipulation de code (referential transparency) . . . Simplicité: Probablement le meilleur indicateur de réussite de projet. *** Production Ready™ - rapide - équivalent à Java (~ x2 du C) - parfois plus rapide que C - bien plus rapide que python et ruby . . . - communauté solide - 3k comptes sur Haskellers - >30k sur reddit /(35k rust, 45k go, 50k nodejs, 4k ocaml, 13k clojure)/ - libs >12k sur hackage . . . - entreprises - Facebook (fighting spam, HAXL, ...) - beaucoup de startups, finance en général . . . - milieu académique - fondations mathématiques - fortes influences des chercheurs - tire le langage vers le haut *** Tooling - compilateur (GHC) - gestion de projets ; cabal, stack, hpack, etc... - IDE / hlint ; rapidité des erreurs en cours de frappe - frameworks hors catégorie (servant, yesod) - ecosystèmes très matures et inovant - Elm (⇒ frontend) - Purescript (⇒ frontend) - GHCJS (⇒ frontend) - Idris (types dépendants) - Hackett (typed LISP avec macros) - Eta (⇒ JVM) *** Qualité #+BEGIN_QUOTE /Si ça compile alors il probable que ça marche/ #+END_QUOTE . . . - tests unitaires : chercher quelques erreurs manuellements . . . - /test génératifs/ : chercher des erreurs sur beaucoups de cas générés aléatoirements & aide pour trouver l'erreur sur l'objet le plus simple . . . - /finite state machine generative testing/ : chercher des erreurs sur le déroulement des actions entre différents agents indépendants . . . - *preuves*: chercher des erreur sur *TOUTES* les entrées possibles possible à l'aide du système de typage * Premiers Pas en Haskell *** DON'T PANIC #+BEGIN_SRC ██████╗ ██████╗ ███╗ ██╗████████╗ ██████╗ █████╗ ███╗ ██╗██╗ ██████╗██╗ ██╔══██╗██╔═══██╗████╗ ██║╚══██╔══╝ ██╔══██╗██╔══██╗████╗ ██║██║██╔════╝██║ ██║ ██║██║ ██║██╔██╗ ██║ ██║ ██████╔╝███████║██╔██╗ ██║██║██║ ██║ ██║ ██║██║ ██║██║╚██╗██║ ██║ ██╔═══╝ ██╔══██║██║╚██╗██║██║██║ ╚═╝ ██████╔╝╚██████╔╝██║ ╚████║ ██║ ██║ ██║ ██║██║ ╚████║██║╚██████╗██╗ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═════╝╚═╝ #+END_SRC - Haskell peut être difficile à vraiment maîtriser - Trois languages en un: - Fonctionnel - Imperatif - Types - Polymorphisme: - contexte souvent semi-implicite change le comportement du code. *** Fichier de script isolé Avec Stack: https://haskellstack.org #+BEGIN_SRC haskell #!/usr/bin/env stack {- stack script --resolver lts-12.10 --install-ghc --package protolude -} #+END_SRC Avec Nix: https://nixos.org/nix/ #+BEGIN_SRC shell #! /usr/bin/env nix-shell #! nix-shell -i runghc #! nix-shell -p "ghc.withPackages (ps: [ ps.protolude ])" #! nix-shell -I nixpkgs="https://github.com/NixOS/nixpkgs/archive/18.09.tar.gz" #+END_SRC *** Hello World! (1/3) #+BEGIN_SRC haskell -- hello.hs main :: IO () main = putStrLn "Hello World!" #+END_SRC #+BEGIN_SRC > chmod +x hello.hs > ./hello.hs Hello World! #+END_SRC #+BEGIN_SRC > stack ghc -- hello.hs > ./hello Hello World! #+END_SRC *** Hello World! (2/3) #+BEGIN_SRC haskell main :: IO () main = putStrLn "Hello World!" #+END_SRC - ~::~ de type ; - le type de ~main~ est ~IO ()~. - ~=~ égalité (la vrai, on peut interchanger ce qu'il y a des deux cotés) ; - le type de ~putStrLn~ est ~String -> IO ()~ ; - application de fonction =f x= pas =f(x)=, pas de parenthèse nécessaire ; *** Hello World! (3/3) #+BEGIN_SRC haskell main :: IO () main = putStrLn "Hello World!" #+END_SRC - Le type ~IO a~ signifie: C'est une description d'une procédure qui quand elle est évaluée peut faire des actions d'IO qui retournera une valeur de type ~a~ ; - ~main~ est le nom du point d'entrée du programme ; - Haskell runtime va chercher pour ~main~ et l'exécute. ** What is your name? *** What is your name? (1/2) #+BEGIN_SRC haskell main :: IO () main = do putStrLn "Hello! What is your name?" name <- getLine let output = "Nice to meet you, " ++ name ++ "!" putStrLn output #+END_SRC . . . - l'indentation est importante ! - ~do~ commence une syntaxe spéciale qui permet de séquencer des actions ~IO~ ; - le type de ~getLine~ est ~IO String~ ; - ~IO String~ signifie: Ceci est la description d'une procédure qui lorsqu'elle est évaluée peut faire des actions IO et retourne une valeur de type ~String~. *** What is your name? (2/2) #+BEGIN_SRC haskell main :: IO () main = do putStrLn "Hello! What is your name?" name <- getLine let output = "Nice to meet you, " ++ name ++ "!" putStrLn output #+END_SRC - le type de ~getLine~ est ~IO String~ - le type de ~name~ est ~String~ - ~<-~ est une syntaxe spéciale qui n'apparait que dans la notation ~do~ - ~<-~ signifie: évalue la procédure et attache la valeur renvoyée dans le nom à gauche de ~<-~ - ~let = ~ signifie que ~name~ est interchangeable avec ~expr~ pour le reste du bloc ~do~. - dans un bloc ~do~, ~let~ n'a pas besoin d'être accompagné par ~in~ à la fin. ** Erreurs classiques *** Erreur classique #1 #+BEGIN_SRC haskell main :: IO () main = do putStrLn "Hello! What is your name?" let output = "Nice to meet you, " ++ getLine ++ "!" putStrLn output #+END_SRC #+BEGIN_SRC /Users/yaesposi/.deft/pres-haskell/name.hs:6:40: warning: [-Wdeferred-type-errors] • Couldn't match expected type ‘[Char]’ with actual type ‘IO String’ • In the first argument of ‘(++)’, namely ‘getLine’ In the second argument of ‘(++)’, namely ‘getLine ++ "!"’ In the expression: "Nice to meet you, " ++ getLine ++ "!" | 6 | let output = "Nice to meet you, " ++ getLine ++ "!" | ^^^^^^^ Ok, one module loaded. #+END_SRC *** Erreur classique #1 - ~String~ est ~[Char]~ - Haskell n'arrive pas à faire matcher le type ~String~ avec ~IO String~. - ~IO a~ et ~a~ sont différents *** Erreur classique #2 #+BEGIN_SRC haskell main :: IO () main = do putStrLn "Hello! What is your name?" name <- getLine putStrLn "Nice to meet you, " ++ name ++ "!" #+END_SRC #+BEGIN_SRC /Users/yaesposi/.deft/pres-haskell/name.hs:7:3: warning: [-Wdeferred-type-errors] • Couldn't match expected type ‘[Char]’ with actual type ‘IO ()’ • In the first argument of ‘(++)’, namely ‘putStrLn "Nice to meet you, "’ In a stmt of a 'do' block: putStrLn "Nice to meet you, " ++ name ++ "!" In the expression: do putStrLn "Hello! What is your name?" name <- getLine putStrLn "Nice to meet you, " ++ name ++ "!" | 7 | putStrLn "Nice to meet you, " ++ name ++ "!" #+END_SRC *** Erreur classique #2 (fix) - Des parenthèses sont nécessaires - L'application de fonction se fait de gauche à droite #+BEGIN_SRC haskell main :: IO () main = do putStrLn "Hello! What is your name?" name <- getLine putStrLn ("Nice to meet you, " ++ name ++ "!") #+END_SRC * Concepts avec exemples *** Concepts - /style déclaratif & récursif/ - /immutabilité/ - /pureté/ (par défaut) - /evaluation paraisseuse/ (par défaut) - /ADT & typage polymorphique/ *** /Style déclaratif & récursif/ #+BEGIN_SRC python >>> x=0 ... for i in range(1,11): ... tmp = i*i ... if tmp%2 == 0: ... x += tmp >>> x 220 #+END_SRC #+BEGIN_SRC haskell -- (.) composition (de droite à gauche) Prelude> sum . filter even . map (^2) $ [1..10] 220 Prelude> :set -XNoImplicitPrelude Prelude> import Protolude -- (&) flipped fn application (de gauche à droite) Protolude> [1..10] & map (^2) & filter even & sum 220 #+END_SRC *** /Style déclaratif & récursif/ #+BEGIN_SRC python >>> x=0 ... for i in range(1,11): ... j = i*3 ... tmp = j*j ... if tmp%2 == 0: ... x += tmp #+END_SRC #+BEGIN_SRC haskell Prelude> sum . filter even . map (^2) . map (*3) $ [1..10] Protolude> [1..10] & map (*3) & map (^2) & filter even & sum #+END_SRC *** /Style déclaratif & récursif/ - Contrairement aux languages impératifs la récursion n'est généralement pas chère. - tail recursive function, mais aussi à l'aide de la lazyness *** /Imutabilité/ #+BEGIN_SRC haskell -- | explicit recursivity incrementAllEvenNumbers :: [Int] -> [Int] incrementAllEvenNumbers (x:xs) = y:incrementAllEvenNumbers xs where y = if even x then x+1 else x -- | better with use of higher order functions incrementAllEvenNumbers' :: [Int] -> [Int] incrementAllEvenNumbers' ls = map incrementIfEven ls where incrementIfEven :: Int -> Int incrementIfEven x = if even x then x+1 else x #+END_SRC *** /Pureté/: Function vs Procedure/Subroutines - Une /fonction/ n'a pas d'effet de bord - Une /Procedure/ ou /subroutine/ but engendrer des effets de bords lors de son évaluation *** /Pureté/: Function vs Procedure/Subroutines (exemple) #+BEGIN_SRC haskell dist :: Double -> Double -> Double dist x y = sqrt (x**2 + y**2) #+END_SRC #+BEGIN_SRC haskell getName :: IO String getName = readLine #+END_SRC - *IO a* ⇒ *IMPUR* ; effets de bords hors evaluation : - lire un fichier ; - écrire sur le terminal ; - changer la valeur d'une variable en RAM est impur. *** /Pureté/: Gain, paralellisation gratuite #+BEGIN_SRC haskell import Foreign.Lib (f) -- f :: Int -> Int -- f = ??? foo = sum results where results = map f [1..100] #+END_SRC . . . *~pmap~ FTW!!!!! Assurance d'avoir le même résultat avec 32 cœurs* #+BEGIN_SRC haskell import Foreign.Lib (f) -- f :: Int -> Int -- f = ??? foo = sum results where results = pmap f [1..100] #+END_SRC *** /Pureté/: Structures de données immuable Purely functional data structures, /Chris Okasaki/ Thèse en 1996, et un livre. Opérations sur les listes, tableaux, arbres de complexité amortie equivalent ou proche (pire des cas facteur log(n)) de celle des structures de données muables. *** /Évaluation parraisseuse/: Stratégies d'évaluations =(h (f a) (g b))= peut s'évaluer: - =a= → =(f a)= → =b= → =(g b)= → =(h (f a) (g b))= - =b= → =a= → =(g b)= → =(f a)= → =(h (f a) (g b))= - =a= et =b= en parallèle puis =(f a)= et =(g b)= en parallèle et finallement =(h (f a) (g b))= - =h= → =(f a)= seulement si nécessaire et puis =(g b)= seulement si nécessaire Par exemple: =(def h (λx.λy.(+ x x)))= il n'est pas nécessaire d'évaluer =y=, dans notre cas =(g b)= *** /Évaluation parraisseuse/: Exemple #+BEGIN_SRC haskell quickSort [] = [] quickSort (x:xs) = quickSort (filter (=x) xs) minimum list = head (quickSort list) #+END_SRC Un appel à ~minimum longList~ ne vas pas ordonner toute la liste. Le travail s'arrêtera dès que le premier élément de la liste ordonnée sera trouvé. ~take k (quickSort list)~ est en ~O(n + k log k)~ où ~n = length list~. Alors qu'avec une évaluation stricte: ~O(n log n)~. *** /Évaluation parraisseuse/: Structures de données infinies (zip) #+BEGIN_SRC haskell zip :: [a] -> [b] -> [(a,b)] zip [] _ = [] zip _ [] = [] zip (x:xs) (y:ys) = (x,y):zip xs ys #+END_SRC #+BEGIN_SRC haskell zip [1..] ['a','b','c'] #+END_SRC s'arrête et renvoie : #+BEGIN_SRC haskell [(1,'a'), (2,'b'), (3, 'c')] #+END_SRC *** /Évaluation parraisseuse/: Structures de données infinies (2) #+BEGIN_SRC haskell Prelude> zipWith (+) [0,1,2,3] [10,100,1000] [10,101,1002] Prelude> take 3 [1,2,3,4,5,6,7,8,9] [1,2,3] #+END_SRC #+BEGIN_SRC haskell Prelude> fib = 0:1:(zipWith (+) fib (tail fib)) Prelude> take 10 fib [0,1,1,2,3,5,8,13,21,34] #+END_SRC *** /ADT & Typage polymorphique/ Algebraic Data Types. #+BEGIN_SRC haskell data Void = Void Void -- 0 valeur possible! data Unit = () -- 1 seule valeur possible data Product x y = P x y data Sum x y = S1 x | S2 y #+END_SRC Soit ~#x~ le nombre de valeurs possibles pour le type ~x~ alors: - ~#(Product x y) = #x * #y~ - ~#(Sum x y) = #x + #y~ *** /ADT & Typage polymorphique/: Inférence de type À partir de : #+BEGIN_SRC haskell zip [] _ = [] zip _ [] = [] zip (x:xs) (y:ys) = (x,y):zip xs ys #+END_SRC le compilateur peut déduire: #+BEGIN_SRC haskell zip :: [a] -> [b] -> [(a,b)] #+END_SRC ** Composabilité *** Composabilité vs Modularité Modularité: soit un ~a~ et un ~b~, je peux faire un ~c~. ex: x un graphique, y une barre de menu => une page ~let page = mkPage ( graphique, menu )~ Composabilité: soit deux ~a~ je peux faire un autre ~a~. ex: x un widget, y un widget => un widget ~let page = x <+> y~ Gain d'abstraction, moindre coût. *Hypothèses fortes sur les ~a~* *** Exemples - *Semi-groupes* 〈+〉 - *Monoides* 〈0,+〉 - *Catégories* 〈obj(C),hom(C),∘〉 - Foncteurs ~fmap~ (~(<$>)~) - Foncteurs Applicatifs ~ap~ (~(<*>)~) - Monades ~join~ - Traversables ~map~ - Foldables ~reduce~ * Catégories de bugs évités avec Haskell *** Real Productions Bugs™ Bug vu des dizaines de fois en prod malgré: 1. specifications fonctionnelles 2. spécifications techniques 3. tests unitaires 4. 3 envs, dev, recette/staging/pre-prod, prod 5. Équipe de QA qui teste en recette Solutions simples. *** **Null Pointer Exception**: Erreur classique (1) Au début du projet : #+BEGIN_SRC javascript int foo( x ) { return x + 1; } #+END_SRC *** **Null Pointer Exception**: Erreur classique (2) Après quelques semaines/mois/années : #+BEGIN_SRC javascript import do_shit_1 from "foreign-module"; int foo( x ) { ... var y = do_shit_1(x); ... return do_shit_20(y) } ... var val = foo(26/2334 - Math.sqrt(2)); #+END_SRC . . . #+BEGIN_SRC ███████ █████ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ██ ███ ███ ███ ███ ████ ████ ███ ███ ███ ███ ███ ███ ██ ███ ███ ███ ███ █████ █████ ███ ███ ███ ███ ███ ███████ ███ ███ ███ ███ ███ █████ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █ ███ █ █ █ █ █ ███ ███ ███ ███ ███ ███ ███ ███ ███████ █████ █████ ███ ███ ███ ███ ███ ███ ███ #+END_SRC | *Null Pointer Exception* *** Null Pointer Exception: Data type ~Maybe~ #+BEGIN_SRC haskell data Maybe a = Just a | Nothing ... foo :: Maybe a ... myFunc x = let t = foo x in case t of Just someValue -> doThingsWith someValue Nothing -> doThingWhenNothingIsReturned #+END_SRC Le compilateur oblige à tenir compte des cas particuliers! Impossible d'oublier. *** Null Pointer Exception: Etat - Rendre impossibe de fabriquer un état qui devrait être impossible d'avoir. - Pour aller plus loin voir, FRP, CQRS/ES, Elm-architecture, etc... *** Erreur due à une typo #+BEGIN_SRC haskell data Foo x = LongNameWithPossibleError x ... foo (LongNameWithPosibleError x) = ... #+END_SRC *Erreur à la compilation*: Le nom d'un champ n'est pas une string (voir les objets JSON). *** Echange de parameters #+BEGIN_SRC haskell data Personne = Personne { uid :: Int, age :: Int } foo :: Int -> Int -> Personne -- ??? uid ou age? #+END_SRC #+BEGIN_SRC haskell newtype UID = UID Int deriving (Eq) data Personne = Personne { uid :: UID, age :: Int } foo :: UDI -> Int -> Personne -- Impossible de confondre #+END_SRC *** Changement intempestif d'un Etat Global #+BEGIN_SRC haskell foo :: GlobalState -> x #+END_SRC *~foo~ ne peut pas changer =GlobalState=* * Organisation du Code *** Grands Concepts Procedure vs Functions: | Gestion d'une configuration globale | | Gestion d'un état global | | Gestion des Erreurs | | Gestion des IO | *** Monades Pour chacun de ces /problèmes/ il existe une monade: | Gestion d'une configuration globale | ~Reader~ | | Gestion d'un état global | ~State~ | | Gestion des Erreurs | ~Either~ | | Gestion des IO | ~IO~ | *** Effets Gestion de plusieurs Effets dans la même fonction: - MTL - Free Monad - Freer Monad Idée: donner à certaines sous-fonction accès à une partie des effets seulement. Par exemple: - limiter une fonction à la lecture de la DB mais pas l'écriture. - limiter l'écriture à une seule table - interdire l'écriture de logs - interdire l'écriture sur le disque dur - etc... *** Exemple dans un code réel (1) #+BEGIN_SRC haskell -- | ConsumerBot type, the main monad in which the bot code is written with. -- Provide config, state, logs and IO type ConsumerBot m a = ( MonadState ConsumerState m , MonadReader ConsumerConf m , MonadLog (WithSeverity Doc) m , MonadBaseControl IO m , MonadSleep m , MonadPubSub m , MonadIO m ) => m a #+END_SRC *** Exemple dans un code réel (2) #+BEGIN_SRC haskell bot :: Manager -> RotatingLog -> Chan RedditComment -> TVar RedbotConfs -> Severity -> IO () bot manager rotLog pubsub redbots minSeverity = do TC.setDefaultPersist TC.filePersist let conf = ConsumerConf { rhconf = RedditHttpConf { _connMgr = manager } , commentStream = pubsub } void $ autobot & flip runReaderT conf & flip runStateT (initState redbots) & flip runLoggingT (renderLog minSeverity rotLog) #+END_SRC ** Règles *pragmatiques* *** Organisation en fonction de la complexité #+BEGIN_QUOTE Make it work, make it right, make it fast #+END_QUOTE - Simple: directement IO (YOLO!) - Medium: Haskell Design Patterns: The Handle Pattern: https://jaspervdj.be/posts/2018-03-08-handle-pattern.html - Medium (bis): MTL / Free / Freeer / Effects... - Gros: Three Layer Haskell Cake: http://www.parsonsmatt.org/2018/03/22/three_layer_haskell_cake.html + Layer 1: Imperatif + Orienté Objet (Level 2 / 2') + Fonctionnel *** 3 couches - *Imperatif*: ReaderT IO + Insérer l'état dans une ~TVar~, ~MVar~ ou ~IORef~ (concurrence) - *Orienté Objet*: + Handle / MTL / Free... + donner des access ~UserDB~, ~AccessTime~, ~APIHTTP~... - *Fonctionnel*: Business Logic ~f : Handlers -> Inputs -> Command~ *** Services / Lib Service: ~init~ / ~start~ / ~close~ + methodes... Lib: methodes sans état interne. * Conclusion *** Pourquoi Haskell? - avantage compétitif: qualité & productivité hors norme - change son approche de la programmation - les concepts appris sont utilisables dans tous les languages - permet d'aller là où aucun autre langage ne peut vous amener - Approfondissement sans fin: - Théorie: théorie des catégories, théorie des types homotopiques, etc... - Optim: compilateur - Qualité: tests, preuves - Organisation: capacité de contraindre de très haut vers très bas *** Avantage compétitif - France, Europe du sud & Functional Programming - Coût Maintenance >> production d'un nouveau produit - Coût de la refactorisation - "Make it work, Make it right, Make it fast" moins cher. *** Pour la suite A chacun de choisir, livres, tutoriels, videos, chat, etc... - Voici une liste de resources : https://www.haskell.org/documentation - Mon tuto rapide : [[http://yannesposito.com/Scratch/en/blog/Haskell-the-Hard-Way/][Haskell the Hard Way]] - Moteurs de recherche par type : [[http://hayoo.fh-wedel.de][hayoo]] & [[http://haskell.org/hoogle][hoogle]] - Communauté & News : http://haskell.org/news & ~#haskell-fr~ sur freenode - Libs: https://hackage.haskell.org & https://stackage.org * Appendix *** STM: Exemple (Concurrence) (1/2) #+BEGIN_SRC java class Account { float balance; synchronized void deposit(float amount){ balance += amount; } synchronized void withdraw(float amount){ if (balance < amount) throw new OutOfMoneyError(); balance -= amount; } synchronized void transfert(Account other, float amount){ other.withdraw(amount); this.deposit(amount); } } #+END_SRC Situation d'interblocage typique. (A transfert vers B et B vers A). *** STM: Exemple (Concurrence) (2/2) #+BEGIN_SRC haskell deposit :: TVar Int -> Int -> STM () deposit acc n = do bal <- readTVar acc writeTVar acc (bal + n) withdraw :: TVar Int -> Int -> STM () withdraw acc n = do bal <- readTVar acc if bal < n then retry writeTVar acc (bal - n) transfer :: TVar Int -> TVar Int -> Int -> STM () transfer from to n = do withdraw from n deposit to n #+END_SRC - pas de lock explicite, composition naturelle dans ~transfer~. - si une des deux opération échoue toute la transaction échoue - le système de type force cette opération a être atomique: ~atomically :: STM a -> IO a~