Comment lire la notation des systèmes de types ?
(langdev.stackexchange.com)- Même si la notation des systèmes de types varie selon les documents, connaître le cadre commun — grammaire, relation de typage et règles d’inférence — permet de suivre la plupart des variantes
- Un système de types opère sur la syntaxe abstraite d’un langage ; il faut donc d’abord distinguer, dans la grammaire, les termes (
term) qui ont un type et les types eux-mêmes ⊢ e: τest un jugement de typage signifiant « l’expressionea le typeτ» ; il se lit comme une règle d’inférence où, si toutes les conditions au-dessus de la barre horizontale sont vraies, alors la conclusion en dessous l’est aussi- Avec l’introduction de variables et de fonctions, on ajoute un contexte, comme dans
Γ ⊢ e: τ, afin de suivre les noms de variables et leurs types dans la portée courante - Beaucoup de règles de typage peuvent se lire comme une fonction récursive de vérification de types, mais tout jugement logique ne se transforme pas directement en algorithme de vérification de types décidable
La notation des systèmes de types part de la grammaire
- Un système de types est un système syntaxique d’un langage de programmation : un ensemble de règles qui opèrent sur la syntaxe abstraite du langage
- Une description complète d’un système de types commence généralement par présenter les structures syntaxiques concernées sous forme de grammaire, souvent avec la notation BNF
- Même dans le langage typé le plus simple, la syntaxe se divise en deux grandes catégories
e: les expressions qui ont un typeτ: les types attachés aux expressions
- Le langage d’exemple possède comme expressions des littéraux booléens, des littéraux entiers, des expressions conditionnelles, des opérations arithmétiques et des comparaisons, et utilise
BooletIntcomme types - Selon les documents, le symbole de type peut être écrit
t,T,σou avec d’autres lettres grecques minuscules au lieu deτ, mais la structure générale reste similaire - Les langages plus complexes peuvent inclure davantage de catégories syntaxiques, comme les instructions ou les motifs de pattern matching
Lire les relations et jugements de typage
- Après avoir défini la grammaire, on définit généralement une relation de typage de la forme
e : τ1 + 2 : Intsignifie que «1 + 2est de typeInt»1 + 2 : Boolaffirme que la même expression est de typeBool, ce qui est incorrecttrue + 2 : Intn’a pas de sens en tant qu’expression et n’a donc aucun type
⊢ e : τest un jugement de typage, et⊢peut se lire comme « l’énoncé qui suit est vrai »- Une règle qui n’a rien au-dessus de la barre horizontale est un axiome, toujours vrai
⊢ true : Bool⊢ false : Bool- Les règles pour les littéraux entiers, comme
⊢ 0 : Int,⊢ 1 : Int,⊢ -1 : Int
- Une règle avec des éléments au-dessus et en dessous de la barre est une règle d’inférence
- Si toutes les conditions au-dessus sont vraies, alors la conclusion en dessous est vraie
- Si
e₁ete₂sont tous deux de typeInt, alorse₁ + e₂est de typeInt - Si
e₁ete₂sont tous deux de typeInt, alorse₁ < e₂est de typeBool
Expressions conditionnelles et variables de type
- Les deux branches de
if ... then ... else ...peuvent être de n’importe quel type, mais elles doivent avoir le même typeif true then 1 else 2est possibleif true then false else trueest possibleif true then 1 else trueest impossible
- Pour l’exprimer, la règle utilise une variable
τreprésentant le type des branches- L’expression conditionnelle
e₁doit être de typeBool - La branche
thene₂et la brancheelsee₃doivent avoir le même typeτ - Le type de l’expression conditionnelle entière est aussi
τ
- L’expression conditionnelle
- Lorsqu’on applique la règle, on peut choisir n’importe quel type pour
τ, mais il faut conserver ce choix de manière cohérente dans la même règle
Lire les règles d’inférence comme un algorithme
- Cette notation vient de la logique formelle, et la manière de spécifier les systèmes de types ressemble particulièrement à la déduction naturelle
- Ces règles servent à construire des preuves formelles sur les propriétés du système, et sont importantes pour prouver des propriétés comme la sûreté de typage
- Un jugement logique ne correspond pas toujours directement à un algorithme de vérification de types décidable
- Dans de nombreux cas, on peut lire
⊢ e : τcomme une fonction qui obtient le typeτà partir de l’expressione- Il existe généralement une règle pour chaque forme d’expression de la grammaire
- Chaque règle de typage peut être vue comme une branche d’une fonction récursive de vérification de types
- La fonction
inferde l’exemple correspond au déroulé suivanttrueoufalsedonneBool- Un littéral entier donne
Int e₁ + e₂donneIntaprès avoir vérifié que les résultats d’inférence des deux côtés sont tous deuxInte₁ < e₂donneBoolaprès avoir vérifié que les deux côtés sont de typeIntif e₁ then e₂ else e₃vérifie que la condition est de typeBool, puis que les deux branches ont le même type, et renvoie ce type
- Même lorsqu’il n’est pas possible de le transformer directement en algorithme, considérer
ecomme une entrée etτcomme une sortie dans le jugement facilite la compréhension du flux d’information
Variables et contexte
- Pour traiter des langages de programmation utiles, il faut des variables ; l’exemple est étendu avec des fonctions pour prendre la forme du lambda-calcul simplement typé
- La grammaire étendue inclut les éléments suivants
- Variable
x - Abstraction de fonction
λx:τ. e - Application de fonction
e e - Type fonction
τ → τ
- Variable
λx:τ. ecorrespond à(x:τ) => een TypeScript, etf xcorrespond àf(x)- Le type d’une variable dépend du contexte dans lequel elle apparaît ; on ne peut donc pas écrire une règle sous la simple forme
⊢ x : ??? - Le jugement de typage est donc étendu en
Γ ⊢ e : τΓest le contexte, ou environnement de types⊢sépare les hypothèses contextuelles à gauche de l’énoncé à prouver à droite- Il se lit : « sous le contexte
Γ, l’expressionea le typeτ»
- Algorithmiquement,
Γpeut être vu comme une entrée supplémentaire de la formeMap<Variable, Type> - Formellement, le contexte est lui aussi spécifié comme une structure syntaxique
∅: contexte videΓ, x:τ: contexte auquel on a ajouté un binding de variable- Parfois,
•est utilisé comme contexte vide au lieu de∅
- Dans cette représentation, le contexte ressemble à une liste d’association qui associe des noms de variables à des types
Le rôle du contexte dans les règles
- Beaucoup de règles de typage transmettent le contexte tel quel, sans le modifier
Γ ⊢ true : Bool- Si
Γ ⊢ e₁ : IntetΓ ⊢ e₂ : Int, alorsΓ ⊢ e₁ + e₂ : Int
- Le contexte joue un rôle central dans les règles d’utilisation des variables et des lambdas
- Si
x:τ ∈ Γ, alorsΓ ⊢ x : τ - Si
Γ, x:τ₁ ⊢ e : τ₂, alorsΓ ⊢ (λx:τ₁. e) : τ₁ → τ₂
- Si
- Lorsqu’on vérifie le type du corps
ed’une lambda, le contexte est étendu avec le nouveau bindingx:τ₁ - La règle des variables juge que, si un binding de variable existe dans le contexte courant, cette variable a le type correspondant
- Le contexte sert de mécanisme de communication entre la règle des lambdas et la règle des variables
- Pour simplifier, les spécifications de systèmes de types de ce genre supposent généralement que toutes les variables ont déjà été résolues et rendues uniques, et ne traitent pas le masquage de variables
- La règle d’application de fonction vérifie conjointement le type de l’expression de fonction et celui de l’expression argument
e₁doit être de typeτ₁ → τ₂e₂doit être de typeτ₁- Le type de l’application complète
e₁ e₂devientτ₂
Notations supplémentaires fréquentes
- Les règles d’inférence ne sont pas toujours écrites uniquement à la verticale
- Plusieurs conditions peuvent être disposées côte à côte horizontalement
- Les dispositions verticales et horizontales peuvent être mélangées dans une même règle
- Les conditions au-dessus de la barre sont généralement d’autres jugements, mais il peut aussi s’agir de conditions annexes (
side conditions) booléennes arbitrairesx:τ ∈ Γdans la règle des variables en est un exemple- Dans les systèmes de types algorithmiques, on peut voir
α fresh, qui signifie queαdoit être une nouvelle variable de type, distincte des autres variables de type
Sous-typage
- Le sous-typage est une relation qui traite la compatibilité entre types de manière moins stricte que l’égalité exacte, et doit être défini explicitement
- Il s’écrit généralement
τ₁ <: τ₂et se lit : «τ₁est un sous-type deτ₂» - Une relation de sous-typage simple peut introduire un type sommet
⊤et un type fond⊥τ <: τ: tout type est un sous-type de lui-mêmeτ <: ⊤: tout type est un sous-type de⊤⊥ <: τ:⊥est un sous-type de tout type
- La première règle est la règle de réflexivité, souvent abrégée en
refl - Pour autoriser le sous-typage, il faut utiliser explicitement cette relation dans chaque règle de typage qui l’accepte
- Dans la règle d’application de fonction, on peut autoriser l’application si le type de l’argument
τ₁est un sous-type du type du paramètreτ₂
- Dans la règle d’application de fonction, on peut autoriser l’application si le type de l’argument
Contextes multiples et vérification bidirectionnelle des types
- Certains systèmes de types définissent des jugements de typage comportant plusieurs contextes
- Le second contexte est généralement appelé
Δ Γ;Δ ⊢ e : τest souvent utilisé lorsque les deux contextes servent d’entréesΓ ⊢ e : τ ⊣ Δest souvent utilisé lorsqueΔsert de sortie
- Le second contexte est généralement appelé
- Le second contexte est utilisé différemment selon les cas
- Il peut permettre de ne référencer certaines variables qu’à l’intérieur d’une expression donnée
- Dans les langages de programmation sensibles aux ressources, il peut servir de contexte de sortie pour suivre les variables qui ont été consommées
- La vérification bidirectionnelle des types est une approche qui réalise une inférence de types non locale limitée sans solveur de contraintes
- Les systèmes bidirectionnels divisent le jugement général
Γ ⊢ e : τen deux jugements spécialisésΓ ⊢ e ⇐ τ: jugement de vérification (checking) qui vérifie que l’expressionea le type attenduτ; algorithmiquement,τest une entréeΓ ⊢ e ⇒ τ: jugement d’inférence utilisé lorsqu’il n’y a pas d’information de type attendue ; algorithmiquement,τest une sortie
- Les deux jugements sont définis par récursion mutuelle et propagent l’information de type dans les deux directions
- Avec cette approche, certaines annotations de type peuvent être omises ; la règle de vérification des abstractions lambda peut obtenir le type du paramètre à partir du type fonction attendu, ce qui permet d’omettre l’annotation du binder de variable
1 commentaires
Avis sur Hacker News
Guy Steele avait déjà donné une présentation sur ce sujet. Il avait même donné à certaines notations des noms qu’on peut rechercher, comme diagrammes bidimensionnels de règles d’inférence
Il appelle cela une métanotation pour l’informatique, mais personnellement, ça me semble plus proche de la théorie des langages de programmation. https://m.youtube.com/watch?v=dCuZkaaou0Q
https://en.wikipedia.org/wiki/Guy_Steele Guy Steele
https://www.codemesh.io/codemesh2017/guy-l-steele conférence "A Cobbler's Child" à Code Mesh 2017
https://www.youtube.com/watch?v=qNPlDnX6Mio "A Cobbler's Child" (vidéo sur YouTube)
https://www.youtube.com/watch?v=dCuZkaaou0Q "It's Time for a New Old Language" (vidéo sur YouTube)
https://news.ycombinator.com/item?id=15473199 discussion sur HN
https://labs.oracle.com/pls/apex/f?p=94065:40150:0::::P40150... diapositives
Il est étrange que des gens qui cherchent à parler avec la plus grande précision de l’art humain le plus précis utilisent une notation ambiguë et incohérente
Cette notation remonte jusqu’à Frege. C’est difficile à chercher si l’on ne sait pas quoi chercher, mais cet article semble être un assez bon résumé : https://plato.stanford.edu/entries/frege-logic
Le symbole de tourniquet
|-était déjà utilisé, et la ligne horizontale qu’on appelait en cours « Fregescher Schlussstrich », c’est-à-dire le trait de conclusion de Frege, semble avoir fait à l’origine partie du tourniquet lui-même avant de devenir un élément séparé dans la notation moderneTypes and Programming Languages de Benjamin C. Pierce est un bon manuel qui couvre ce genre de choses
Même en ayant fait des études d’informatique, je suis toujours embrouillé par la différence de sens entre
|–et|=, et par le niveau de métasyntaxe auquel se trouvent chacune des variables utiliséesIroniquement, l’une des causes est que la notation elle-même n’a pas de types explicites
Pour ceux qui hésitent à lire : cet article explique la notation des systèmes de types qu’on trouve dans les articles d’informatique, et c’est en fait une introduction à la notation BNF, aux règles d’inférence, etc. pour les systèmes de types
Ça ressemble à un bon résumé
Je comprends le concept logique de l’application des types, mais comme je ne lis pas souvent des articles d’informatique, l’association entre les symboles et leur sens ne se fixe pas bien dans ma tête
Dans les exemples,
𝗍𝗋𝗎𝖾+2:𝖨𝗇𝗍signifie «𝗍𝗋𝗎𝖾+2est de type𝖨𝗇𝗍», mais il est dit que c’est d’autant plus étrange que l’expression𝗍𝗋𝗎𝖾+2elle-même n’a pas de sens et n’a pas non plus de typePourtant, en Python,
True + 2est bel et bien un entier, et sa valeur est 3. Que cela doive être le cas ou non est une autre question, mais dans les faits c’est ainsiTrue + 2a du sens, il suffit de définir soi-même une règle de jugement qui l’autoriseLa logique et la théorie des systèmes de types ne s’intéressent pas en soi aux axiomes et règles d’inférence que l’on choisit ; elles permettent seulement de raisonner sur ces règles et leurs interactions. Par exemple, on peut poser
|- True : Bool,|- True : Int, ou, si l’on veut ne l’autoriser que dans certaines expressions, faire dériver|- True + x: Intà partir de|- x : Inttrueest mappé sur 1, donctrue+1=2True + 2ne produit pas d’erreur en Python ou en C, cela reste idiot, parce qu’on rend la sémantique du langage plus difficile à raisonner juste pour donner un peu de sucre syntaxique au programmeurTrès bien. Cela faisait des années que je me posais la question, mais je ne savais pas quels mots-clés utiliser pour en savoir plus
Parfois, quand quelqu’un publie gratuitement un savoir ésotérique qu’on a eu du mal à acquérir, on se sent bêtement contrarié ;) J’aurais vraiment aimé avoir ce genre d’article quand j’apprenais cela. J’espère qu’une meilleure accessibilité réduira le nombre de langages mal fichus
En lisant l’Ada Reference Manual, j’ai immédiatement reconnu ce genre de syntaxe. Je n’en connaissais pas le nom, mais c’était intéressant de la voir dans un cas d’usage réel, et tout le langage est défini avec cette notation
Exemple : https://ada-lang.io/docs/arm/AA-3/AA-3.7#syntax
L’Ada Reference Manual explicite la notation qu’il utilise. Il emploie une variante de la forme de Backus-Naur, et la section liée décrit cette variante précise
Ça me semble être un bon endroit pour prêcher en faveur d’une colline sur laquelle j’ai décidé de mourir. Dans le format d’annotation de type qui utilise deux-points, les espaces de part et d’autre du deux-points devraient être identiques
Pour moi, il se trouve simplement qu’il existe deux signes différents qui ont la même apparence, à savoir deux points superposés. L’un est le deux-points d’étiquette, où, comme en anglais, la partie précédente introduit la suivante, ou la gauche est l’étiquette de la droite ; c’est le cas du début de bloc en Python, des paires clé-valeur, ou des paires nom-valeur dans les structs en C ou Rust
L’autre est l’annotation de type empruntée aux mathématiques. C’est une relation binaire, et les relations binaires prennent le même espacement à gauche et à droite. De même qu’on n’écrit pas
x= 1,x> youx+ z, il est plus naturel d’écrirex : Xplutôt quex: XQuand je vois
a: b, je le lis immédiatement comme un deux-points d’étiquette, et lorsqu’il s’agit d’une annotation de type, cela demande à chaque fois une conversion mentale supplémentaire, minuscule mais réelle. Je parle ici de syntaxe de langages de programmation, et personnellement je préfère de loinx : XàX x[1] « Evangelion » est un mot élégant qui vient de εὐαγγέλιον, c’est-à-dire « bonne nouvelle ». [2] https://en.wikipedia.org/wiki/Colon_(punctuation)#Usage_in_E...
f: X->Y, avec plus d’espace à droite du deux-points ; sur les 3 livres que j’ai vérifiés, 1 n’utilisait que cette notationDe plus, cela reste assez proche d’un étiquetage : on étiquette un morphisme d’une forme donnée. Le cas où, en mathématiques, le deux-points a vraiment un autre sens, c’est lorsqu’il sert d’abréviation de
such that, par exemple dans des définitions d’ensembles comme{ x : x \in IN and x | 2}, ou souvent avec des quantificateursX x.x: Xme paraît beaucoup plus naturel, et plus proche aussi de la façon dont on utilise le deux-points en langue naturelleIl y a une proposition, et ce qui suit le deux-points l’explique plus en détail ; comme le type est lui aussi une information supplémentaire sur ce qui se trouve à gauche, ça colle parfaitement
t[espace]:[espace]TLa théorie des types dans son ensemble a des aspects incohérents et assez brouillons, mais c’est l’un des rares cas où tout le monde est plutôt cohérent. Par curiosité, j’ai regardé comment je l’écrivais à l’époque de la licence, et je l’avais moi aussi écrit joliment de façon symétrique : https://dvt.name/logic/horse2.pdf
x: Xcorrespond à l’usage où « une explication vient après le deux-points »Autrement dit, quelque chose comme
variable x: It’s an X.age: intse reformule facilement en anglais comme « person’s age: an integer »C’est pourquoi le deux-points ne m’a jamais vraiment gêné