894 lines
24 KiB
Org Mode
894 lines
24 KiB
Org Mode
#+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: <link rel='stylesheet' type='text/css' href='/css/slides.css' />
|
||
* 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-beta.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 <name> = <expr>~ 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)
|
||
++ [x]
|
||
++ 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~
|