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

Made with Slides.com