<2018-03-15 Thu>
Initialiser l’env de dev:
curl -sSL https://get.haskellstack.org/ | sh
stack new ipfh https://git.io/vbpej && \
cd ipfh && \
stack setup && \
stack build && \
stack test && \
stack bench
+--------------------------------+
| +----------------------------+ |
| | central processing unit | |
| | +------------------------+ | |
| | | Control Unit | | |
+------+ | | +------------------------+ | | +--------+
|input +---> | +------------------------+ | +--> output |
+------+ | | | Arithmetic/Logic Unit | | | +--------+
| | +------------------------+ | |
| +-------+---^----------------+ |
| | | |
| +-------v---+----------------+ |
| | Memory Unit | |
| +----------------------------+ |
+--------------------------------+
made with http://asciiflow.com
pieds nus (code machine, ASM)
_
/ \
/. ) _
___/ | / / \
.-'__/ |( ( .\
\ | \___
)| \__`-.
Talons hauts (C, Pascal, Java, C++, Perl, PHP, Python, Ruby, etc…)
Tennis (Clojure, Scheme, LISP, etc…)
Voiture (Haskell, Purescript, etc…)
/!\
SIMPLICITÉ ≠ FACILITÉ /!\
Si ça compile alors il probable que ça marche
module Main where
main :: IO ()
main = putStrLn "Hello World!"
file:~/.deft/pres-haskell/hello.hs
module Main where
main :: IO ()
main = putStrLn "Hello World!"
::
de type ;=
égalité (la vrai, on peut interchanger ce qu’il y a des deux cotés) ;putStrLn
est String -> IO ()
;main
est IO ()
.module Main where
main :: IO ()
main = putStrLn "Hello World!"
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 ;main
et l’exécute.module Main where
main :: IO ()
main = do
putStrLn "Hello! What is your name?"
name <- getLine
let output = "Nice to meet you, " ++ name ++ "!"
putStrLn output
module Main where
main :: IO ()
main = do
putStrLn "Hello! What is your name?"
name <- getLine
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 à la fin retourne une valeur de type String
.module Main where
main :: IO ()
main = do
putStrLn "Hello! What is your name?"
name <- getLine
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.module Main where
main :: IO ()
main = do
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érentsmodule Main where
main :: IO ()
main = do
putStrLn "Hello! What is your name?"
name <- getLine
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 ++ "!"
module Main where
main :: IO ()
main = do
putStrLn "Hello! What is your name?"
name <- getLine
putStrLn ("Nice to meet you, " ++ name ++ "!")
dist :: Double -> Double -> Double
dist x y = sqrt (x**2 + y**2)
getName :: IO String
getName = readLine
import Foreign.Lib (f)
-- f :: Int -> Int
-- f = ???
foo = sum results
where results = map f [1..100]
fmap
FTW!!!!! Assurance d’avoir le même résultat avec 32 cœurs
import Foreign.Lib (f)
-- f :: Int -> Int
-- f = ???
foo = sum results
where results = fmap 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 [] = []
quickSort (x:xs) = quickSort (filter (<x) xs)
++ [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')]
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
Monoides 〈0,+〉
fmap
((<$>)
)ap
((<*>)
)join
map
Foldables reduce
Bug vu des dizaines de fois en prod malgré:
Solutions simples.
int foo( x ) {
return x + 1;
}
int foo( x ) {
...
var y = do_shit_1(x);
...
return do_shit_20(x)
}
...
var val = foo(26/2334 - Math.sqrt(2));
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
Null Pointer Exception
Maybe
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
Le compilateur oblige à tenir compte des cas particuliers! Impossible d’oublier.
data Foo x = LongNameWithPossibleError x
...
foo (LongNameWithPosibleError x) = ...
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 ()
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)
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.
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); }
}
Situation d’interblocage typique. (A transfert vers B et B vers A).
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
transfer
.atomically :: STM a -> IO a