857 lines
21 KiB
Org Mode
857 lines
21 KiB
Org Mode
#+Title: Introduction à la Programmation Fonctionnelle en Haskell
|
||
#+Author: Yann Esposito
|
||
#+Date: <2018-03-15 Thu>
|
||
|
||
* Courte Introduction
|
||
** Prelude
|
||
|
||
Initialiser l'env de dev:
|
||
|
||
#+BEGIN_SRC shell
|
||
curl -sSL https://get.haskellstack.org/ | sh
|
||
stack new ipfh https://goo.gl/HNzncC && \
|
||
cd ipfh && \
|
||
stack setup && \
|
||
stack build && \
|
||
stack test && \
|
||
stack bench
|
||
#+END_SRC
|
||
|
||
** Parcours jusqu'à Haskell
|
||
*** Parcours Pro
|
||
|
||
- Doctorat (machine learning, hidden markov models) 2004
|
||
- Post doc (écriture d'un UI pour des biologistes en Java). 2006
|
||
- Dev Airfrance, (Perl, scripts shell, awk, HTML, CSS, JS, XML...) 2006 → 2013
|
||
- Dev (ruby, C, ML) pour GridPocket. (dev) 2009 → 2011, (impliqué) 2009 →
|
||
- Clojure dev & Machine Learning pour Vigiglobe. 2013 → 2016
|
||
- Senior Clojure développeur chez Cisco. 2016 →
|
||
|
||
*** Langages de programmations basiques
|
||
|
||
1. BASIC (MO5, Amstrad CPC 6129, Atari STf)
|
||
2. Logo (école primaire, + écriture d'un cours en 1ère année de Fac)
|
||
3. Pascal (lycée, fac)
|
||
4. C (fac)
|
||
5. ADA (fac)
|
||
|
||
*** Langages de programmations orientés objet
|
||
|
||
6. C++ (fac + outils de recherche pour doctorat)
|
||
7. Eiffel (fac)
|
||
8. Java (fac, UI en Java 1.6, Swing pour postdoc)
|
||
9. Objective-C (temps personnel, app iPhone, app Mac, Screensavers)
|
||
|
||
*** Langages moderne de script
|
||
|
||
10. PHP (fac, site perso)
|
||
11. Python (fac, projets perso, jeux, etc...)
|
||
12. Awk (fac, Airfrance, ...)
|
||
13. Perl (Airfrance...)
|
||
14. Ruby (GridPocket, site perso v2)
|
||
15. Javascript:
|
||
- /Airfrance/ basic prototype, jquery, etc..,
|
||
- spine.js
|
||
- backbone.js
|
||
- Coffeescript
|
||
- Cappuccino (Objective-J)
|
||
- Sproutcore
|
||
- /Vigiglobe/ actionhero (nodejs), angularjs v1
|
||
|
||
*** Langage peu (re)connus
|
||
|
||
18. Metapost
|
||
19. zsh (quasi lang de prog)
|
||
20. prolog
|
||
|
||
|
||
*** Langages fonctionnels
|
||
|
||
16. CamL
|
||
17. Haskell (Vigiglobe, personnal)
|
||
18. Clojure (Vigiglobe, Cisco)
|
||
|
||
** Qu'est-ce que la programmation fonctionnelle?
|
||
|
||
*** Von Neumann Architecture
|
||
|
||
#+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
|
||
|
||
*** Retour d'expérience subjectif
|
||
|
||
/pieds nus/ (code machine, ASM)
|
||
|
||
. . .
|
||
|
||
#+BEGIN_SRC
|
||
_
|
||
/ \
|
||
/. ) _
|
||
___/ | / / \
|
||
.-'__/ |( ( .\
|
||
\ | \___
|
||
)| \__`-.
|
||
#+END_SRC
|
||
|
||
/Talons hauts/ (C, Pascal, Java, C++, Perl, PHP, Python, Ruby, etc...)
|
||
|
||
. . .
|
||
|
||
/Tennis/ (Clojure, Scheme, LISP, etc...)
|
||
|
||
. . .
|
||
|
||
/Voiture/ (Haskell, Purescript, etc...)
|
||
|
||
** 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)
|
||
|
||
*** 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)
|
||
|
||
*** Qualité
|
||
|
||
#+BEGIN_QUOTE
|
||
/Si ça compile alors il probable que ça marche/
|
||
#+END_QUOTE
|
||
. . .
|
||
- test unitaires :
|
||
chercher quelques erreurs manuellements
|
||
. . .
|
||
- /test génératifs/ :
|
||
chercher des erreurs sur beaucoups de cas générés aléatoirement
|
||
& 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
|
||
|
||
*** Hello World! (1/3)
|
||
|
||
#+BEGIN_SRC haskell
|
||
module Main where
|
||
|
||
main :: IO ()
|
||
main = putStrLn "Hello World!"
|
||
#+END_SRC
|
||
|
||
[[file:~/.deft/pres-haskell/hello.hs]]
|
||
|
||
*** Hello World! (2/3)
|
||
|
||
#+BEGIN_SRC haskell
|
||
module Main where
|
||
|
||
main :: IO ()
|
||
main = putStrLn "Hello World!"
|
||
#+END_SRC
|
||
|
||
- ~::~ de type ;
|
||
- ~=~ égalité (la vrai, on peut interchanger ce qu'il y a des deux cotés) ;
|
||
- le type de ~putStrLn~ est ~String -> IO ()~ ;
|
||
- le type de ~main~ est ~IO ()~.
|
||
|
||
*** Hello World! (3/3)
|
||
|
||
#+BEGIN_SRC haskell
|
||
module Main where
|
||
|
||
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 et finalement retourne 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/3)
|
||
|
||
#+BEGIN_SRC haskell
|
||
module Main where
|
||
|
||
main :: IO ()
|
||
main = do
|
||
putStrLn "Hello! What is your name?"
|
||
name <- getLine
|
||
let output = "Nice to meet you, " ++ name ++ "!"
|
||
putStrLn output
|
||
#+END_SRC
|
||
|
||
file:pres-haskell/name.hs
|
||
|
||
*** What is your name? (2/3)
|
||
|
||
|
||
#+BEGIN_SRC haskell
|
||
module Main where
|
||
|
||
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 à la fin retourne une valeur de type
|
||
~String~.
|
||
|
||
*** What is your name? (3/3)
|
||
|
||
#+BEGIN_SRC haskell
|
||
module Main where
|
||
|
||
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
|
||
module Main where
|
||
|
||
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
|
||
module Main where
|
||
|
||
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
|
||
module Main where
|
||
|
||
main :: IO ()
|
||
main = do
|
||
putStrLn "Hello! What is your name?"
|
||
name <- getLine
|
||
putStrLn ("Nice to meet you, " ++ name ++ "!")
|
||
#+END_SRC
|
||
|
||
* Concepts avec exemples
|
||
|
||
*** Concepts
|
||
- /pureté/ (par défaut)
|
||
- /evaluation paraisseuse/ (par défaut)
|
||
- /ADT & typage polymorphique/
|
||
*** /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 1
|
||
|
||
#+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
|
||
|
||
*** /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)
|
||
|
||
#+BEGIN_SRC javascript
|
||
int foo( x ) {
|
||
return x + 1;
|
||
}
|
||
#+END_SRC
|
||
|
||
*** Null Pointer Exception: Erreur classique (2)
|
||
|
||
#+BEGIN_SRC javascript
|
||
int foo( x ) {
|
||
...
|
||
var y = do_shit_1(x);
|
||
...
|
||
return do_shit_20(x)
|
||
}
|
||
...
|
||
var val = foo(26/2334 - Math.sqrt(2));
|
||
#+END_SRC
|
||
|
||
. . .
|
||
|
||
#+BEGIN_SRC
|
||
888888b. .d88888b. 888 888 888b d888 888 888 888 888 888
|
||
888 "88b d88P" "Y88b 888 888 8888b d8888 888 888 888 888 888
|
||
888 .88P 888 888 888 888 88888b.d88888 888 888 888 888 888
|
||
8888888K. 888 888 888 888 888Y88888P888 888 888 888 888 888
|
||
888 "Y88b 888 888 888 888 888 Y888P 888 888 888 888 888 888
|
||
888 888 888 888 888 888 888 Y8P 888 Y8P Y8P Y8P Y8P Y8P
|
||
888 d88P Y88b. .d88P Y88b. .d88P 888 " 888 " " " " "
|
||
8888888P" "Y88888P" "Y88888P" 888 888 888 888 888 888 888
|
||
#+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 Excepton: 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é x productivité hors norme
|
||
- changera 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
|
||
- 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~
|