L'intérêt du
typage statique
Jeroen Engels (Yeuroune)
C'est quoi le typage ?
Type = Ensemble de valeurs possibles
age : Int
age = 29
nom : String
nom = "Jeroen Engels"
Typage dynamique
Dynamique : Vérification au cours de l'exécution (JavaScript, Python, Clojure, ...)
# Python
>>> "string" + 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot concatenate 'str' and 'int' objects
Typage statique
Statique : Vérifiés avant l'exécution
(Elm, Java, Haskell, ...)
Syntaxe Elm
Déclarations de fonctions
maFonction : TypeParametre1 -> TypeParametre2 -> TypeDeRetour
maFonction parametre1 parametre2 =
valeurDeLaFonction
Appel de fonctions
variable : Type
variable =
maFonction argument1 argument2
Définition de type
type alias Prenom = String
type alias Nom =
{ prenom : Prenom
, age : Int
}
type Sexe
= Homme
| Femme
| Autre String
Intérêts du typage statique
Trouver des erreurs tôt
quantite : String
quantite = 100 -- NON!
Maintenabilité
"Ça compile, donc ça marche"
Moins de tests
Seulement sur la logique métier
Documentation
Toujours à jour
prenom : String -- Garantie par le compilateur
prenom =
"Jeroen"
-- Si jamais on change
prenom : String
prenom =
1000 -- Erreur de compilation
Fournit une documentation minimale, même si le code est compliqué
a : (Username -> Username) -> User -> User
a b c =
d (b (e c)) c
Délimite les possibilités
addition : number -> number -> number
nomUtilisateur : User -> String
modifiePrenom : (Username -> Username) -> User -> User
Créer des garanties
Créer nos propres types
Créer nos propres contraintes
=
Garantie
Contrainte
=
Modélisation de données
type alias RequeteAuServeur data =
{ enCours : Bool
, enErreur : Bool
, données : Maybe data
}
maRequete : RequeteAuServeur String
maRequete =
{ enCours = True
, enErreur = True
, données = Just "Jeroen"
}
Custom types
type RequeteAuServeur data
= NonDemarree
| EnCours
| EnErreur
| EnSucces data
Making Impossible States Impossible
par Richard Feldman
"Ça ne résoud pas mes
problèmes de logique métier"
Problèmes de logique métier
Problème de types
type alias User =
{ nom : String
-- "admin" ou "user"
, role : String
}
peutSupprimerBaseDeDonnees : User -> Bool
peutSupprimerBaseDeDonnees user =
if user.role == "user" then
False
else
True
user : User
user =
{ nom = "Jeroen Engels"
, role = "User"
}
type alias User =
{ nom : String
, role : Role
}
type Role
= Admin
| User
peutSupprimerBaseDeDonnees : User -> Bool
peutSupprimerBaseDeDonnees user =
case user.role of
Admin -> True
User -> False
type alias User =
{ idUser : String
, idVoiture : String
}
nouvelleVoiture : String -> User -> User
nouvelleVoiture idVoiture user =
{ user | idUser = idVoiture }
Mélanger des valeurs similaires
type alias User =
{ idUser : UserId
, idVoiture : VoitureId
}
type UserId
= UserId String
type VoitureId
= VoitureId String
nouvelleVoiture : VoitureId -> User -> User
nouvelleVoiture idVoiture user =
{ user | idUser = idVoiture } -- Erreur
Éviter "primitive obsession"
Valeurs de types primitifs
type alias User =
{ nom : String
, motDePasse : String
}
modifieMotDePasse : String -> User -> User
modifieMotDePasse motDePasse user =
{ user | motDePasse = motDePasse }
utilisateurAvecMotDePasse =
modifieMotDePasse "a" utilisateur
Garantir un mot de passe fort
type alias User =
{ nom : String
, motDePasse : MotDePasseFort
}
type MotDePasseFort
= MotDePasseFort String
modifieMotDePasse : MotDePasseFort -> User -> User
modifieMotDePasse motDePasse user =
{ user | motDePasse = motDePasse }
utilisateurAvecMotDePasse =
modifieMotDePasse (MotDePasseFort "a") utilisateur
Garantir un mot de passe fort
module MotDePasseFort exposing
(MotDePasseFort, fromString, comparerMotDePasse, hash)
-- Ne pas faire MotDePasseFort(..)
type MotDePasseFort
= MotDePasseFort String
{-| Si mdp est fort, alors retourne Just mdp, sinon Nothing -}
fromString : String -> Maybe MotDePasseFort
comparerMotDePasse : String -> MotDePasseFort -> Bool
hash : MotDePasseFort -> String
Mot de passe fort avec des types opaques
Mot de passe fort avec des types opaques
type alias User =
{ nom : String
, motDePasse : MotDePasseFort
}
modifieMotDePasse : MotDePasseFort -> User -> User
modifieMotDePasse motDePasse user =
{ user | motDePasse = motDePasse }
utilisateurAvecMotDePasse =
case MotDePasseFort.fromString "a" of
Just mdp ->
modifieMotDePasse mdp utilisateur
Nothing ->
utilisateur
Types opaques
Limite les opérations possibles sur un type
Cacher l'implémentation (encapsulation)
Opaque par défaut
Never
type Never
= JustOneMore Never
Never
type Never
= JustOneMore Never
a : Never
a =
JustOneMore (JustOneMore (JustOneMore (...
staticHtml : List (Html Never) -> Html Never
"Type variables"
type RequeteAuServeur data
= NonDemarree
| EnChargement
| EnErreur
| EnSucces data
reponse : RequeteAuServeur String
reponseAvecUtilisateur : RequeteAuServeur User
Type fantômes (Phantom types)
-- a n'est pas utilisé
type Fantome a
= Fantome QuelqueChose
Type fantômes pour des identifiants
type Id data
= Id String
type Voiture = ...
type alias User =
{ voitureId : Id Voiture
}
Type fantômes pour des unités
type Monnaie monnaie
= Monnaie Int
addition : Monnaie m -> Monnaie m -> Monnaie m
euros : Monnaie Euros
euros = Monnaie 1000
dollars : Monnaie Dollars
dollars = Monnaie 100
somme = addition euros dollars -- Erreur!
(Html et Cmd sont des types fantômes)
Builder pattern
type Button msg =
Button
{ label : String
, onClick : msg
-- ...
}
withOnClick : msg -> Button msg -> Button msg
------------------------------------------------
view : Html Msg
view =
Button.new
|> Button.withLabel "Clickez-moi!"
|> Button.withOnClick ButtonHasBeenClicked
|> Button.toHtml
Combinaisons invalides
view : Html Msg
view =
Button.new
|> Button.withLabel "Clickez-moi!"
-- On ne veut pas un bouton désactivé cliquable!
|> Button.withOnClick ButtonHasBeenClicked
|> Button.disabled
|> Button.toHtml
Plus de combinaisons invalides
type Button etat msg =
Button { ... }
new : Button { clickOrDisabled : () } msg
withOnClick :
msg
-> Button { etat | clickOrDisabled : () } msg
-> Button etat msg
disabled :
Button { etat | clickOrDisabled : () } msg
-> Button etat msg
Plus de combinaisons invalides
view : Html Msg
view =
Button.new
|> Button.withLabel "Clickez-moi!"
|> Button.withOnClick ButtonHasBeenClicked
-- Erreur du compilateur
-- "Il manque le champ `clickOrDisabled`"
|> Button.disabled
|> Button.toHtml
Allez faire des programmes corrects !
Jeroen Engels (Yeuroune)
jfmengels sur le Slack Elm
https://slides.com/jeroenengels/typage-statique
jfmengels.net
L'intérêt du typage statique
By Jeroen Engels
L'intérêt du typage statique
- 634