████████████████████████████████████████████████████████████████████████████████████
█ █
█ Initialiser l'env de dev █
█ █
████████████████████████████████████████████████████████████████████████████████████
Install stack:
curl -sSL https://get.haskellstack.org/ | sh
Install nix:
curl https://nixos.org/nix/install | sh
+--------------------------------+
| +----------------------------+ |
| | central processing unit | |
| | +------------------------+ | |
| | | Control Unit | | |
+------+ | | +------------------------+ | | +--------+
|input +---> | +------------------------+ | +--> output |
+------+ | | | Arithmetic/Logic Unit | | | +--------+
| | +------------------------+ | |
| +-------+---^----------------+ |
| | | |
| +-------v---+----------------+ |
| | Memory Unit | |
| +----------------------------+ |
+--------------------------------+
made with http://asciiflow.com
/!\
SIMPLICITÉ ≠ FACILITÉ /!\
Simplicité: Probablement le meilleur indicateur de réussite de projet.
Si ça compile alors il probable que ça marche
██████╗ ██████╗ ███╗ ██╗████████╗ ██████╗ █████╗ ███╗ ██╗██╗ ██████╗██╗
██╔══██╗██╔═══██╗████╗ ██║╚══██╔══╝ ██╔══██╗██╔══██╗████╗ ██║██║██╔════╝██║
██║ ██║██║ ██║██╔██╗ ██║ ██║ ██████╔╝███████║██╔██╗ ██║██║██║ ██║
██║ ██║██║ ██║██║╚██╗██║ ██║ ██╔═══╝ ██╔══██║██║╚██╗██║██║██║ ╚═╝
██████╔╝╚██████╔╝██║ ╚████║ ██║ ██║ ██║ ██║██║ ╚████║██║╚██████╗██╗
╚═════╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═════╝╚═╝
Avec Stack: https://haskellstack.org
#!/usr/bin/env stack
{- stack script
--resolver lts-12.10
--install-ghc
--package protolude
-}
Avec Nix: https://nixos.org/nix/
#! /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"
-- hello.hs
main :: IO ()
= putStrLn "Hello World!" main
> chmod +x hello.hs
> ./hello.hs
Hello World!
> stack ghc -- hello.hs
> ./hello
Hello World!
main :: IO ()
= putStrLn "Hello World!" main
::
de type ;main
est IO ()
.=
égalité (la vrai, on peut interchanger ce qu'il y a des deux cotés) ;putStrLn
est String -> IO ()
;f x
pas f(x)
, pas de parenthèse nécessaire ;main :: IO ()
= putStrLn "Hello World!" main
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 ;main
et l'exécute.main :: IO ()
= do
main putStrLn "Hello! What is your name?"
<- getLine
name let output = "Nice to meet you, " ++ name ++ "!"
putStrLn output
do
commence une syntaxe spéciale qui permet de séquencer des actions IO
;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
.main :: IO ()
= do
main putStrLn "Hello! What is your name?"
<- getLine
name let output = "Nice to meet you, " ++ name ++ "!"
putStrLn output
getLine
est IO String
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
.do
, let
n'a pas besoin d'être accompagné par in
à la fin.main :: IO ()
= do
main putStrLn "Hello! What is your name?"
let output = "Nice to meet you, " ++ getLine ++ "!"
putStrLn output
/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.
String
est [Char]
String
avec IO String
.IO a
et a
sont différentsmain :: IO ()
= do
main putStrLn "Hello! What is your name?"
<- getLine
name putStrLn "Nice to meet you, " ++ name ++ "!"
/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 ++ "!"
main :: IO ()
= do
main putStrLn "Hello! What is your name?"
<- getLine
name putStrLn ("Nice to meet you, " ++ name ++ "!")
>>> x=0
for i in range(1,11):
... = i*i
... tmp if tmp%2 == 0:
... += tmp
... x >>> x
220
-- (.) 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
>>> x=0
for i in range(1,11):
... = i*3
... j = j*j
... tmp if tmp%2 == 0:
... += tmp ... x
Prelude> sum . filter even . map (^2) . map (*3) $ [1..10]
Protolude> [1..10] & map (*3) & map (^2) & filter even & sum
-- | explicit recursivity
incrementAllEvenNumbers :: [Int] -> [Int]
:xs) = y:incrementAllEvenNumbers xs
incrementAllEvenNumbers (xwhere y = if even x then x+1 else x
-- | better with use of higher order functions
incrementAllEvenNumbers' :: [Int] -> [Int]
= map incrementIfEven ls
incrementAllEvenNumbers' ls where
incrementIfEven :: Int -> Int
= if even x then x+1 else x incrementIfEven x
dist :: Double -> Double -> Double
= sqrt (x**2 + y**2) dist x y
getName :: IO String
= readLine getName
import Foreign.Lib (f)
-- f :: Int -> Int
-- f = ???
= sum results
foo where results = map f [1..100]
pmap
FTW!!!!! Assurance d'avoir le même résultat avec 32 cœurs
import Foreign.Lib (f)
-- f :: Int -> Int
-- f = ???
= sum results
foo where results = pmap f [1..100]
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.
(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écessairePar exemple: (def h (λx.λy.(+ x x)))
il n'est pas nécessaire d'évaluer y
, dans notre cas (g b)
= []
quickSort [] :xs) = quickSort (filter (<x) xs)
quickSort (x++ [x]
++ quickSort (filter (>=x) xs)
minimum list = head (quickSort list)
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)
.
zip :: [a] -> [b] -> [(a,b)]
zip [] _ = []
zip _ [] = []
zip (x:xs) (y:ys) = (x,y):zip xs ys
zip [1..] ['a','b','c']
s'arrête et renvoie :
1,'a'), (2,'b'), (3, 'c')] [(
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] [
Prelude> fib = 0:1:(zipWith (+) fib (tail fib))
Prelude> take 10 fib
0,1,1,2,3,5,8,13,21,34] [
Algebraic Data Types.
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
Soit #x
le nombre de valeurs possibles pour le type x
alors:
#(Product x y) = #x * #y
#(Sum x y) = #x + #y
À partir de :
zip [] _ = []
zip _ [] = []
zip (x:xs) (y:ys) = (x,y):zip xs ys
le compilateur peut déduire:
zip :: [a] -> [b] -> [(a,b)]
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
Semi-groupes 〈+〉
Monoides 〈0,+〉
Catégories 〈obj(C),hom(C),∘〉
Foncteurs fmap
((<$>)
)
Foncteurs Applicatifs ap
((<*>)
)
Monades join
Traversables map
Foldables reduce
Bug vu des dizaines de fois en prod malgré:
Solutions simples.
Au début du projet :
foo( x ) {
int return x + 1;
}
Après quelques semaines/mois/années :
import do_shit_1 from "foreign-module";
foo( x ) {
int ...
var y = do_shit_1(x);
...
return do_shit_20(y)
}...
var val = foo(26/2334 - Math.sqrt(2));
███████ █████ ███ ███ ███ ███ ███ ███ ███ ███ ███
███ ██ ███ ███ ███ ███ ████ ████ ███ ███ ███ ███ ███
███ ██ ███ ███ ███ ███ █████ █████ ███ ███ ███ ███ ███
███████ ███ ███ ███ ███ ███ █████ ███ ███ ███ ███ ███ ███
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
███ ███ ███ ███ ███ ███ ███ █ ███ █ █ █ █ █
███ ███ ███ ███ ███ ███ ███ ███
███████ █████ █████ ███ ███ ███ ███ ███ ███ ███
Null Pointer Exception |
Maybe
data Maybe a = Just a | Nothing
...
foo :: Maybe a
...
= let t = foo x in
myFunc x case t of
Just someValue -> doThingsWith someValue
Nothing -> doThingWhenNothingIsReturned
Le compilateur oblige à tenir compte des cas particuliers! Impossible d'oublier.
data Foo x = LongNameWithPossibleError x
...
LongNameWithPosibleError x) = ... foo (
Erreur à la compilation: Le nom d'un champ n'est pas une string (voir les objets JSON).
data Personne = Personne { uid :: Int, age :: Int }
foo :: Int -> Int -> Personne -- ??? uid ou age?
newtype UID = UID Int deriving (Eq)
data Personne = Personne { uid :: UID, age :: Int }
foo :: UDI -> Int -> Personne -- Impossible de confondre
foo :: GlobalState -> x
foo
ne peut pas changer GlobalState
Procedure vs Functions:
Gestion d'une configuration globale |
Gestion d'un état global |
Gestion des Erreurs |
Gestion des IO |
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 |
Gestion de plusieurs Effets dans la même fonction:
Idée: donner à certaines sous-fonction accès à une partie des effets seulement.
Par exemple:
-- | 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 )
bot :: Manager
-> RotatingLog
-> Chan RedditComment
-> TVar RedbotConfs
-> Severity
-> IO ()
= do
bot manager rotLog pubsub redbots minSeverity
TC.setDefaultPersist TC.filePersistlet conf = ConsumerConf
= RedditHttpConf { _connMgr = manager }
{ rhconf = pubsub
, commentStream
}$ autobot
void & flip runReaderT conf
& flip runStateT (initState redbots)
& flip runLoggingT (renderLog minSeverity rotLog)
Make it work, make it right, make it fast
TVar
, MVar
ou IORef
(concurrence)UserDB
, AccessTime
, APIHTTP
…f : Handlers -> Inputs -> Command
Service: init
/ start
/ close
+ methodes… Lib: methodes sans état interne.
A chacun de choisir, livres, tutoriels, videos, chat, etc…
#haskell-fr
sur freenodeclass Account {
float balance;
synchronized void deposit(float amount){
+= amount; }
balance synchronized void withdraw(float amount){
if (balance < amount) throw new OutOfMoneyError();
-= amount; }
balance synchronized void transfert(Account other, float amount){
.withdraw(amount);
otherthis.deposit(amount); }
}
Situation d'interblocage typique. (A transfert vers B et B vers A).
deposit :: TVar Int -> Int -> STM ()
= do
deposit acc n <- readTVar acc
bal + n)
writeTVar acc (bal withdraw :: TVar Int -> Int -> STM ()
= do
withdraw acc n <- readTVar acc
bal if bal < n then retry
- n)
writeTVar acc (bal transfer :: TVar Int -> TVar Int -> Int -> STM ()
= do
transfer from to n
withdraw from n deposit to n
transfer
.atomically :: STM a -> IO a