LEÇON 15 / 70 minutes

Modéliser les données et les états

Un bon type rend les états valides faciles à décrire et les états incohérents difficiles à construire. Dans cette leçon, vous allez choisir entre une forme et un choix, puis suivre une valeur de résultat jusque dans son traitement.

À retenir·Utilisez `form` pour des propriétés présentes ensemble et `pick` pour des variantes exclusives.

Décrire les choses qui coexistent avec form

Une forme rassemble les propriétés d’un même objet. Un point possède une coordonnée horizontale et une coordonnée verticale au même moment :

form Point {
    x: i64,
    y: i64,
}

proc somme(point: Point) -> i64 {
    return point.x + point.y;
}

Le nom Point donne un type aux deux nombres et permet de les transmettre ensemble. Les noms des champs rendent leurs rôles visibles ; une signature qui reçoit Point exprime plus clairement son intention que deux paramètres numériques anonymes.

Une forme ne garantit pas à elle seule toutes les règles métier. Un rectangle qui stocke un coin minimum et un coin maximum peut encore avoir un minimum supérieur à son maximum. Il faut décider où cette règle sera vérifiée : à la construction, dans une procédure de validation ou dans un contrat adapté.

Décrire les alternatives avec pick

Un choix nommé représente une valeur qui se trouve dans une variante à la fois. Voici un résultat qui contient soit une valeur, soit un échec :

pick Resultat {
    Ok(i64),
    Echec,
}

proc valeur(resultat: Resultat) -> i64 {
    match resultat {
        Resultat::Ok(nombre) => { return nombre; },
        Resultat::Echec => { return -1; },
    }
    return -2;
}

Dans le bras Ok, nombre reçoit la charge utile. Dans le bras Echec, aucune valeur n’est disponible. Ce modèle distingue un résultat absent d’une valeur numérique ordinaire. Le code -1 de cette fonction n’est qu’une convention de l’exemple ; dans une API, renvoyer un autre pick peut préserver la distinction entre résultat et échec.

La différence de représentation change le raisonnement

Supposons qu’un téléchargement puisse être en attente, terminé ou échoué. Trois booléens peuvent représenter huit combinaisons ; plusieurs ne veulent rien dire, comme « terminé et en attente ». Un pick Etat { EnAttente, Termine, Echec } nomme seulement les cas utiles.

Si une variante doit transporter une information, donnez-lui une charge utile adaptée : un échec peut contenir un code ou une position. Gardez chaque donnée près de la variante à laquelle elle appartient. Cette structure évite qu’un champ d’erreur reste renseigné alors que l’opération a réussi.

Dans l’autre sens, ne transformez pas en variantes des propriétés qui peuvent coexister. Un utilisateur qui a un identifiant, un nom et une adresse est une forme, même si chacune de ces valeurs peut être modifiée séparément.

Lire puis décomposer l’exemple vérifié

Le fichier formes-choix.vit combine les deux idées : il construit un Point, puis construit Resultat::Ok(42) et récupère la charge utile avec match. Le fichier complet s’exécute avec le frontend natif :

build/bin/vitte run examples/tutorials/formes-choix.vit --quiet --no-color

Résultat confirmé : code de sortie 0. La procédure valeur renvoie la charge utile dans le bras Ok et une valeur de repli dans le bras Echec. La lecture des champs du Point constitue l’autre partie de l’exemple.

Une méthode de conception en quatre décisions

  • Les propriétés présentes simultanément sont regroupées dans une forme.
  • Les états exclusifs sont représentés par les variantes d’un choix.
  • Une information propre à un état est placée dans la charge utile de cette variante.
  • Chaque invariant persistant est associé à un point de validation.

Deux ou trois valeurs concrètes révèlent ce que le type doit représenter. Les combinaisons qui n’ont pas de sens révèlent les invariants que le modèle devrait rendre explicites. Si une combinaison invalide reste facile à construire, le modèle ou sa procédure de validation peut être amélioré.

Modéliser une transition, pas seulement un état

Un choix décrit les états possibles ; les procédures décrivent les transitions permises entre eux. Pour une réservation, on peut définir une opération confirmer qui accepte l’état disponible et refuse un état déjà annulé. Le type seul ne précise pas quelles transitions sont permises : il faut les écrire dans la logique de l’application et les vérifier avec des exemples.

Une table de transition résume les états sources, les commandes possibles et les résultats associés :

État courantOpérationRésultat attendu
DisponibleconfirmerConfirmée
ConfirméeannulerAnnulée
AnnuléeconfirmerRefus ou résultat d’erreur

Cette table évite de coder une branche qui n’a jamais été décidée. Si une transition invalide peut arriver normalement, représente son refus dans la sortie de la procédure. Si elle révèle une erreur de programmation, un contrat peut être plus adapté.

Placer une donnée dans la variante qui la justifie

Supposons que seule une annulation ait une raison. Si la raison est un champ de la forme, elle doit recevoir une valeur même quand la réservation est disponible ou confirmée. Un pick peut au contraire placer cette donnée dans la variante qui en a besoin. Les types avec charge utile existent dans le dépôt, comme Resultat::Ok(i64).

Évitez de dupliquer la même information dans un champ et dans une variante. Deux sources de vérité peuvent diverger après une modification. Choisissez l’endroit qui est naturellement propriétaire de la règle et adapte les procédures d’accès à ce modèle.

Vérifier la construction réelle d’un modèle

Le fichier formes-choix.vit fournit une preuve de construction d’une forme, de lecture de ses champs, de construction d’un pick avec charge utile et de récupération de cette charge dans match. Cette preuve est plus forte qu’une déclaration isolée dans la grammaire, mais elle reste limitée aux types et opérations exercés par ce programme.

Pour une forme imbriquée, un champ mutable, une référence vers une variante ou une correspondance plus complexe, écrivez un petit fichier distinct. Ne déduisez pas qu’une propriété avancée fonctionne parce qu’un exemple voisin fonctionne.

Une règle de modèle à vérifier

Si une réservation est confirmée, elle doit posséder son numéro de confirmation. Un modèle qui garde un numéro facultatif dans toutes les variantes nécessite une règle de validation. Un modèle qui place ce numéro uniquement dans la variante « confirmée » rend la relation plus explicite, sous réserve de vérifier que cette forme est prise en charge par la cible utilisée.

Passer des états à leurs transitions

Un modèle décrit les états possibles ; le comportement du programme décrit comment passer de l’un à l’autre. Une réservation peut être disponible, confirmée ou annulée. La transition « confirmer » n’a de sens que depuis l’état disponible ; l’appliquer à une réservation déjà annulée doit avoir une issue définie. Le type pick Etat rend les états exclusifs visibles, mais il n’impose pas à lui seul quelles transitions sont autorisées.

Une procédure de transition exprime cette règle. Elle peut renvoyer un nouvel état ou un résultat distinct lorsque la transition est refusée. Il faut définir si l’opération modifie l’objet existant, produit une nouvelle valeur ou signale une erreur. Ces choix ont un effet sur les paramètres, la mutabilité et le comportement des appels. Le modèle reste plus facile à maintenir lorsque l’état courant, l’action demandée et la nouvelle valeur sont tous discernables.

Placer chaque donnée là où elle est toujours pertinente

Un identifiant, un nom et une adresse coexistent pour un utilisateur ; ils appartiennent naturellement à une form. Une raison d’annulation n’existe que pour une réservation annulée ; la placer parmi les champs communs oblige à lui attribuer une valeur sans sens dans les autres états. Une variante porteuse de données peut associer la raison au cas concerné. Cela réduit les contradictions entre champs et les vérifications nécessaires à chaque lecture.

Une représentation redondante crée plusieurs sources de vérité. Si l’état indique « confirmé » tandis qu’un booléen séparé indique « non réservé », les mises à jour doivent synchroniser les deux champs. Un seul type pick évite ces combinaisons incompatibles en ne permettant qu’un cas nommé à la fois. Il reste nécessaire de vérifier les garanties réelles du compilateur lorsqu’une variante contient des charges utiles ou est passée par référence.

Faire évoluer un modèle sans casser ses appelants

Ajouter une variante modifie les traitements qui supposent connaître tous les cas. Ajouter un champ peut modifier les sites de construction d’une forme. Renommer un état touche les comparaisons, les match, les exports et potentiellement les valeurs sérialisées par l’application. Le choix d’un nom et d’une représentation est donc aussi une décision d’évolution d’API.

Les exemples de Point et Resultat montrent deux formes différentes de relation. Les champs d’un point coexistent ; la réussite et l’échec sont exclusifs ; la charge utile du succès appartient à sa variante. Cette lecture permet de préserver les états nommés même lorsque l’outil ne vérifie pas encore toute l’exhaustivité ou chaque forme générique. Une documentation précise alors la règle du modèle et indique à part les limites de compilation confirmées.

États invalides et représentation contrôlée

Un modèle peut exclure certaines situations par sa structure, ou les laisser représentables et les détecter par validation. Un pick empêche de stocker simultanément deux variantes exclusives si son fonctionnement est correctement imposé. Une form peut encore représenter des champs dont les valeurs se contredisent. La conception doit donc identifier quelles contraintes sont garanties par la forme du type et lesquelles relèvent d’une procédure.

Une construction centralisée peut réduire le nombre de lieux où l’invariant doit être rétabli après une mutation. Lorsque les champs restent publics ou que l’outil ne prend pas en charge une encapsulation attendue, les appelants peuvent contourner le constructeur. L’invariant doit alors être revérifié aux frontières concernées, ou la représentation doit être adaptée au sous-ensemble supporté.

Comparaisons et transitions explicites

Une transition de réservation peut être décrite comme une relation entre un état initial, une commande et un état résultant. Par exemple, Disponible + Confirmer conduit à Confirmee, alors que Annulee + Confirmer peut produire une erreur. Cette description sépare l’état stocké de l’action demandée et rend visibles les combinaisons autorisées.

Une table de transition est également une référence utile lorsque l’implémentation évolue. Chaque ligne nomme un état source et chaque colonne une commande ; le résultat explique la règle. Le code peut ensuite exprimer la même relation par un match, des procédures ou une autre structure reconnue par le compilateur. La table décrit la sémantique métier, tandis que la matrice de support décrit ce que l’outil compile.