Je jure qu'il y avait un T-shirt à vendre avec les mots immortels:
Quelle partie de
tu ne comprends pas ?
Dans mon cas, la réponse serait ... tout!
En particulier, je vois souvent une notation comme celle-ci dans les articles de Haskell, mais je n'ai aucune idée de ce que cela signifie. Je n'ai aucune idée de quelle branche des mathématiques c'est censé être.
Je reconnais bien sûr les lettres de l'alphabet grec et des symboles tels que "∉" (ce qui signifie généralement que quelque chose n'est pas un élément d'un ensemble).
D'un autre côté, je n'ai jamais vu "⊢" auparavant ( Wikipedia prétend que cela pourrait signifier "partition" ). Je ne connais pas non plus l'utilisation du vinculum ici. (Habituellement, cela dénote une fraction, mais cela ne semble pas être le cas ici.)
Si quelqu'un pouvait au moins me dire par où commencer pour chercher à comprendre ce que cette mer de symboles signifie, ce serait utile.
la source
Réponses:
:
signifie a type∈
signifie est . (De même,∉
signifie «n'est pas dedans».)Γ
est généralement utilisé pour faire référence à un environnement ou un contexte; dans ce cas, il peut être considéré comme un ensemble d'annotations de type, associant un identifiant à son type. Signifie doncx : σ ∈ Γ
que l'environnementΓ
inclut le fait quix
a du typeσ
.⊢
peut être lu comme le prouve ou le détermine.Γ ⊢ x : σ
signifie que l'environnementΓ
détermine qu'ilx
a un typeσ
.,
est un moyen d' inclure des hypothèses supplémentaires spécifiques dans un environnementΓ
.Par conséquent,
Γ, x : τ ⊢ e : τ'
signifie que l'environnementΓ
, avec l'hypothèse supplémentaire et prioritaire quix
a le typeτ
, prouve qu'ile
a le typeτ'
.Comme demandé: priorité de l'opérateur, du plus haut au plus bas:
λ x . e
,∀ α . σ
etτ → τ'
,let x = e0 in e1
et des espaces pour l' application de la fonction.:
∈
et∉
,
(gauche-associatif)⊢
la source
:
et∈
sont très similaires, en ce sens qu'ils signifient qu'une chose est contenue dans une autre chose - un ensemble contient des éléments et un type contient des valeurs, dans un sens. La différence cruciale est que celax ∈ S
signifie qu'un ensembleS
contient littéralement un élémentx
, alorsΓ ⊢ x : T
que celax
peut être déduit pour habiter le typeT
dans le contexteΓ
. Compte tenu de cela, la règle Var se lit comme suit: »Si x est littéralement contenu dans le contexte, il peut (trivialement) en être déduit«.(Γ,(x:τ))⊢(x:σ)
, voir overleaf.com/read/ddmnkzjtnqbd#/61990222Cette syntaxe, bien qu'elle puisse sembler compliquée, est en fait assez simple. L'idée de base vient de la logique formelle: l'expression entière est une implication avec la moitié supérieure étant les hypothèses et la moitié inférieure étant le résultat. Autrement dit, si vous savez que les expressions supérieures sont vraies, vous pouvez conclure que les expressions inférieures sont également vraies.
Symboles
Une autre chose à garder à l'esprit est que certaines lettres ont des significations traditionnelles; en particulier, Γ représente le «contexte» dans lequel vous vous trouvez, c'est-à-dire les types d'autres choses que vous avez vues. Donc, quelque chose comme
Γ ⊢ ...
"l'expression"...
lorsque vous connaissez les types de chaque expressionΓ
.Le
⊢
symbole signifie essentiellement que vous pouvez prouver quelque chose. Il enΓ ⊢ ...
va de même pour une déclaration disant: «Je peux prouver...
dans un contexteΓ
. Ces déclarations sont également appelées jugements de type.Une autre chose à garder à l'esprit: en mathématiques, tout comme ML et Scala,
x : σ
signifie que lex
type aσ
. Vous pouvez le lire comme celui de Haskellx :: σ
.Signification de chaque règle
Donc, sachant cela, la première expression devient facile à comprendre: si nous savons que
x : σ ∈ Γ
(c'est-à-dire,x
a un certain typeσ
dans un certain contexteΓ
), alors nous savons queΓ ⊢ x : σ
(c'est-à-dire, dansΓ
,x
a un typeσ
). Donc vraiment, cela ne vous dit rien de super intéressant; il vous indique simplement comment utiliser votre contexte.Les autres règles sont également simples. Par exemple, prenez
[App]
. Cette règle a deux conditions:e₀
est une fonction d'un certain typeτ
à un certain typeτ'
ete₁
est une valeur de typeτ
. Maintenant , vous savez quel type vous obtiendrez en appliquante₀
àe₁
! Espérons que ce ne soit pas une surprise :).La règle suivante a une nouvelle syntaxe. En particulier,
Γ, x : τ
signifie simplement le contexte constituéΓ
et le jugementx : τ
. Donc, si nous savons que la variablex
a un type deτ
et que l'expressione
a un typeτ'
, nous connaissons également le type d'une fonction qui prendx
et renvoiee
. Cela nous dit simplement quoi faire si nous avons compris quel type de fonction prend et quel type elle renvoie, donc cela ne devrait pas être surprenant non plus.La suivante vous indique simplement comment gérer les
let
instructions. Si vous savez qu'une expressione₁
a un typeτ
tant qu'ellex
a un typeσ
, alors unelet
expression qui se lie localementx
à une valeur de typeσ
aurae₁
un typeτ
. Vraiment, cela vous indique simplement qu'une instruction let vous permet essentiellement d'étendre le contexte avec une nouvelle liaison, ce qui est exactement ce quilet
fait!La
[Inst]
règle traite du sous-typage. Il dit que si vous avez une valeur de typeσ'
et qu'il s'agit d'un sous-type deσ
(⊑
représente une relation d'ordre partiel), alors cette expression est également de typeσ
.La dernière règle concerne la généralisation des types. Un petit côté: une variable libre est une variable qui n'est pas introduite par une instruction let ou lambda dans une expression; cette expression dépend maintenant de la valeur de la variable libre de son contexte. La règle dit que s'il y a une variable
α
qui n'est pas "libre" dans quoi que ce soit dans votre contexte, alors il est sûr de dire que toute expression dont vous connaissez le typee : σ
aura ce type pour n'importe quelle valeur deα
.Comment utiliser les règles
Alors, maintenant que vous comprenez les symboles, que faites-vous de ces règles? Eh bien, vous pouvez utiliser ces règles pour déterminer le type de différentes valeurs. Pour ce faire, examinez votre expression (par exemple
f x y
) et trouvez une règle dont la conclusion (la partie inférieure) correspond à votre affirmation. Appelons la chose que vous essayez de trouver votre "objectif". Dans ce cas, vous examineriez la règle qui se termine pare₀ e₁
. Lorsque vous avez trouvé cela, vous devez maintenant trouver des règles prouvant tout au-dessus de la ligne de cette règle. Ces éléments correspondent généralement aux types de sous-expressions, vous recourez donc essentiellement à certaines parties de l'expression. Vous faites cela jusqu'à ce que vous ayez terminé votre arbre de preuve, ce qui vous donne une preuve du type de votre expression.Donc, toutes ces règles ne spécifient exactement - et dans le détail mathématique habituel: P - comment comprendre les types d'expressions.
Maintenant, cela devrait vous sembler familier si vous avez déjà utilisé Prolog - vous calculez essentiellement l'arbre de preuve comme un interpréteur Prolog humain. Il y a une raison pour laquelle Prolog est appelé "programmation logique"! Ceci est également important car la première façon dont j'ai été présenté à l'algorithme d'inférence HM a été de l'implémenter dans Prolog. C'est en fait étonnamment simple et rend clair ce qui se passe. Vous devriez certainement l'essayer.
Remarque: J'ai probablement fait des erreurs dans cette explication et j'aimerais que quelqu'un les signale. En fait, je couvrirai cela en classe dans quelques semaines, alors je serai plus confiant alors: P.
la source
Γ = {x : τ}
)λy.x : σ → τ
à∀ σ. σ → τ
, mais pas∀ τ. σ → τ
parceτ
est variable libreΓ
. L'article de Wikipedia sur HM l' explique très bien.[Inst]
est un peu inexacte. Ce n'est que ma compréhension jusqu'à présent, mais les sigmas dans les règles[Inst]
et[Gen]
ne se réfèrent pas aux types, mais aux schémas de types . L'⊑
opérateur est donc un ordre partiel sans rapport avec le sous-typage tel que nous le connaissons dans les langages OO. Il est lié à des valeurs polymorphes telles queid = λx. x
. La syntaxe complète d'une telle fonction seraitid = ∀x. λx. x
. Maintenant, nous pouvons évidemment avoir unid2 = ∀xy. λx. x
, oùy
n'est pas utilisé. Ensuiteid2 ⊑ id
, c'est ce que[Inst]
dit la règle.Voir « Fondements pratiques des langages de programmation », chapitres 2 et 3, sur le style de la logique par les jugements et les dérivations. L'intégralité du livre est désormais disponible sur Amazon.
Chapitre 2
Définitions inductives
Les définitions inductives sont un outil indispensable dans l'étude des langages de programmation. Dans ce chapitre, nous développerons le cadre de base des définitions inductives et donnerons quelques exemples de leur utilisation. Une définition inductive consiste en un ensemble de règles permettant de dériver des jugements , ou assertions , de diverses formes. Les jugements sont des déclarations sur un ou plusieurs objets syntaxiques d'un type spécifié. Les règles précisent les conditions nécessaires et suffisantes pour la validité d'un jugement et déterminent donc pleinement sa signification.
2.1 Jugements
Nous commençons par la notion de jugement , ou assertion sur un objet syntaxique. Nous utiliserons de nombreuses formes de jugement, y compris des exemples tels que ceux-ci:
Un jugement déclare qu'un ou plusieurs objets syntaxiques ont une propriété ou sont en relation les uns avec les autres. La propriété ou la relation elle-même est appelée une forme de jugement , et le jugement selon lequel un objet ou des objets ont cette propriété ou se tiennent dans cette relation serait une instance de cette forme de jugement. Une forme de jugement est également appelée prédicat , et les objets constituant une instance en sont les sujets . Nous écrivons un J pour le jugement affirmant que J détient un . Quand il n'est pas important d'insister sur le sujet de l'arrêt, (le texte est coupé ici)
la source
Comment puis-je comprendre les règles Hindley-Milner?
Hindley-Milner est un ensemble de règles sous forme de calcul séquentiel (pas de déduction naturelle) qui démontre que nous pouvons déduire le type (le plus général) d'un programme de la construction du programme sans déclarations de type explicites.
Les symboles et la notation
Tout d'abord, expliquons les symboles et discutons de la priorité des opérateurs
𝜆𝑥.𝑒 signifie que 𝜆 (lambda) est une fonction anonyme qui prend un argument, 𝑥 , et renvoie une expression, 𝑒 .
soit 𝑥 = 𝑒₀ dans 𝑒₁ signifie dans l'expression, 𝑒₁ , remplacez 𝑒₀ partout où 𝑥 apparaît.
⊑ signifie que l'élément précédent est un sous-type (informellement - sous-classe) de ce dernier élément.
Tout au-dessus de la ligne est la prémisse, tout en dessous est la conclusion ( Per Martin-Löf )
Priorité, par exemple
J'ai pris certains des exemples les plus complexes des règles et inséré des parenthèses redondantes qui montrent la priorité:
𝚪 ⊦ 𝑥 : 𝜎 pourrait s'écrire 𝚪 ⊦ ( 𝑥 : 𝜎 )
𝚪 ⊦ let 𝑥 = 𝑒₀ in 𝑒₁ : 𝜏 est équivalent 𝚪 ⊦ (( let ( 𝑥 = 𝑒₀ ) in 𝑒₁ ): 𝜏 )
𝚪 ⊦ 𝜆𝑥.𝑒 : 𝜏 → 𝜏 ' est équivalent 𝚪 ⊦ (( 𝜆𝑥.𝑒 ): ( 𝜏 → 𝜏' ))
Ensuite, de grands espaces séparant les déclarations d'assertion et d'autres conditions préalables indiquent un ensemble de telles conditions préalables, et enfin la ligne horizontale séparant le principe de la conclusion fait apparaître la fin de l'ordre de priorité.
Les règles
Ce qui suit ici sont des interprétations anglaises des règles, chacune suivie d'un retraitement lâche et d'une explication.
Variable
Autrement dit, dans 𝚪, nous savons que 𝑥 est de type 𝜎 parce que 𝑥 est de type 𝜎 dans 𝚪.
Il s'agit essentiellement d'une tautologie. Un nom d'identifiant est une variable ou une fonction.
Application de fonction
Pour reformuler la règle, nous savons que l'application de fonction retourne le type 𝜏 'car la fonction a le type 𝜏 → 𝜏' et obtient un argument de type 𝜏.
Cela signifie que si nous savons qu'une fonction renvoie un type et que nous l'appliquons à un argument, le résultat sera une instance du type que nous savons qu'elle renvoie.
Abstraction de fonction
Encore une fois, quand nous voyons une fonction qui prend 𝑥 et renvoie une expression 𝑒, nous savons qu'elle est de type 𝜏 → 𝜏 'parce que 𝑥 (a 𝜏) affirme que 𝑒 est un 𝜏'.
Si nous savons que 𝑥 est de type 𝜏 et donc qu'une expression 𝑒 est de type 𝜏 ', alors une fonction de 𝑥 renvoyant l'expression 𝑒 est de type 𝜏 → 𝜏'.
Soit déclaration de variable
En gros, 𝑥 est lié à 𝑒₀ dans 𝑒₁ (a 𝜏) parce que 𝑒₀ est un 𝜎, et 𝑥 est un 𝜎 qui affirme que 𝑒₁ est un 𝜏.
Cela signifie que si nous avons une expression 𝑒₀ qui est un 𝜎 (étant une variable ou une fonction), et un nom, 𝑥, également un 𝜎, et une expression 𝑒₁ de type 𝜏, alors nous pouvons substituer 𝑒₀ à 𝑥 partout où il apparaît à l'intérieur de 𝑒₁.
Instanciation
Une expression, 𝑒 est du type parent 𝜎 car l'expression 𝑒 est du sous-type 𝜎 ', et 𝜎 est le type parent de 𝜎'.
Si une instance est d'un type qui est un sous-type d'un autre type, alors c'est également une instance de ce super-type - le type plus général.
Généralisation
Donc en général, 𝑒 est tapé 𝜎 pour toutes les variables d'argument (𝛼) retournant 𝜎, car nous savons que 𝑒 est un 𝜎 et 𝛼 n'est pas une variable libre.
Cela signifie que nous pouvons généraliser un programme pour accepter tous les types d'arguments qui ne sont pas déjà liés dans la portée contenante (variables qui ne sont pas non locales). Ces variables liées sont substituables.
Mettre tous ensemble
Compte tenu de certaines hypothèses (comme l'absence de variables libres / non définies, un environnement connu), nous connaissons les types de:
Conclusion
Ces règles combinées nous permettent de prouver le type le plus général d'un programme affirmé, sans exiger d'annotations de type.
la source
La notation vient de la déduction naturelle .
Le symbole is est appelé tourniquet .
Les 6 règles sont très simples.
Var
La règle est plutôt banale - elle dit que si le type pour l'identifiant est déjà présent dans votre environnement de type, alors pour déduire le type, vous le prenez simplement de l'environnement tel quel.App
La règle dit que si vous avez deux identifiantse0
ete1
que vous pouvez déduire leurs types, vous pouvez déduire le type d'applicatione0 e1
. La règle se lit comme ceci si vous le saveze0 :: t0 -> t1
ete1 :: t0
(le même t0!), Alors l'application est bien typée et le type l'estt1
.Abs
etLet
sont des règles pour déduire des types d'abstraction lambda et d'admission.Inst
La règle dit que vous pouvez remplacer un type par un type moins général.la source
Il y a deux façons de penser à e: σ. L'un est "l'expression e a le type σ", l'autre est "la paire ordonnée de l'expression e et du type σ".
Voir Γ comme la connaissance des types d'expressions, implémentée comme un ensemble de paires d'expression et de type, e: σ.
Le tourniquet ⊢ signifie qu'à partir des connaissances de gauche, nous pouvons déduire ce qui se trouve à droite.
On peut donc lire la première règle [Var]:
Si notre connaissance Γ contient le couple e: σ, alors on peut déduire de Γ que e a le type σ.
La deuxième règle [App] peut être lue:
Si nous de Γ pouvons déduire que e_0 a le type τ → τ ', et nous de Γ pouvons déduire que e_1 a le type τ, alors nous de Γ pouvons déduire que e_0 e_1 a le tapez τ '.
Il est courant d'écrire Γ, e: σ au lieu de Γ ∪ {e: σ}.
La troisième règle [Abs] peut donc être lue:
Si nous de Γ étendu avec x: τ pouvons déduire que e a le type τ ', alors nous de Γ pouvons déduire que λx.e a le type τ → τ'.
La quatrième règle [Let] est laissée comme exercice. :-)
La cinquième règle [Inst] peut être lue:
Si nous de Γ pouvons déduire que e a le type σ ', et σ' est un sous-type de σ, alors nous de Γ pouvons déduire que e a le type σ.
La sixième et dernière règle [Gen] peut être lue:
Si nous de Γ pouvons déduire que e a le type σ, et α n'est une variable de type libre dans aucun des types de Γ, alors nous de Γ pouvons déduire que e a le type ∀α σ.
la source