2019-08-24 14:14:04 +00:00
|
|
|
|
#+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 ])"
|
2019-12-16 12:07:02 +00:00
|
|
|
|
#! nix-shell -I nixpkgs="https://github.com/NixOS/nixpkgs/archive/18.09.tar.gz"
|
2019-08-24 14:14:04 +00:00
|
|
|
|
#+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~
|