De cette référence: Positivité stricte
La stricte condition de positivité exclut les déclarations telles que
data Bad : Set where
bad : (Bad → Bad) → Bad
A B C
-- A is in a negative position, B and C are OK
Pourquoi A est-il négatif? Aussi pourquoi B est-il autorisé? Je comprends pourquoi C est autorisé.
A
la pile et éventuellement l'exploser (dans les langages basés sur la pile).Réponses:
D'abord une explication terminologique: les positions négatives et positives viennent de la logique. Ils sont sur une asymétrie dans connecteurs logiques: en l' A se comporte différemment de B . Une chose similaire se produit dans la théorie des catégories, où nous disons contravariant et covariant au lieu de négatif et positif, respectivement. En physique, ils parlent de quantités qui se comportent "de manière covariante" et "contravariante aussi. C'est donc un phénomène très général. Un programmeur pourrait les considérer comme" entrée "et" sortie ".A ⇒ B UNE B
Passons maintenant aux types de données inductifs.
Considérez un type de données inductive comme une sorte de structure algébrique: constructeurs sont les opérations qui ont des éléments de T comme arguments et produire de nouveaux éléments de T . C'est très similaire à l'algèbre ordinaire: l'addition prend deux nombres et produit un nombre.T T T
En algèbre, il est habituel qu'une opération prenne un nombre fini d'arguments, et dans la plupart des cas, elle prend zéro (constant), un (unaire) ou deux (binaire) arguments. Il est commode de généraliser cela pour les constructeurs de types de données. Supposons que
c
c'est un constructeur pour un type de donnéesT
:c
est une constante, nous pouvons la considérer comme une fonctionunit -> T
, ou de manière équivalente(empty -> T) -> T
,c
est unaire, nous pouvons le considérer comme une fonctionT -> T
, ou de manière équivalente(unit -> T) -> T
,c
est binaire, nous pouvons le considérer comme une fonctionT -> T -> T
, ou de manière équivalenteT * T -> T
, ou équivalente(bool -> T) -> T
,c
qui prend sept arguments, nous pourrions le voir comme une fonction(seven -> T) -> T
où seseven
trouve un type précédemment défini avec sept éléments.c
qui prend une infinité d'arguments, ce serait une fonction(nat -> T) -> T
.Ces exemples montrent que la forme générale d'un constructeur doit être
où nous appelons
A
l' arité dec
et nous pensonsc
comme un constructeur qui prendA
-nombreux arguments de typeT
pour produire un élément deT
.Voici quelque chose de très important: les arités doivent être définies avant de définir
T
, sinon nous ne pouvons pas dire ce que les constructeurs sont censés faire. Si quelqu'un essaie d'avoir un constructeurpuis la question "combien d'arguments
broken
faut-il?" n'a pas de bonne réponse. Vous pouvez essayer d'y répondre avec "il fautT
-de nombreux arguments", mais cela ne fonctionnera pas, car ilT
n'est pas encore défini. Nous pourrions essayer de sortir du cunundrum en utilisant une théorie de point fixe sophistiquée pour trouver un typeT
et une fonction injective(T -> T) -> T
, et réussirions, mais nous briserions également le principe d'induction pour leT
long du chemin. Donc, c'est juste une mauvaise idée d'essayer une telle chose.c
B
En effet, de nombreux constructeurs peuvent être réécrits de cette façon, mais pas tous, nous avons besoin d'une étape de plus, à savoir que nous devrions permettre
A
de dépendre deB
:Il s'agit de la forme finale d'un constructeur pour un type inductif. C'est aussi précisément ce que sont les types W. La forme est si générale que nous n'avons besoin que d'un seul constructeur
c
! En effet, si nous en avons deuxalors nous pouvons les combiner en un seul
où
Soit dit en passant, si nous curry la forme générale, nous voyons qu'elle est équivalente à
ce qui est plus proche de ce que les gens écrivent réellement dans les assistants de preuve. Les assistants de preuve nous permettent d'écrire les constructeurs de manière pratique, mais ceux-ci sont équivalents à la forme générale ci-dessus (exercice!).
la source
La première occurrence de
Bad
est appelée «négative» car elle représente un argument de fonction, c'est-à-dire qu'elle est située à gauche de la flèche de fonction (voir Types récursifs gratuitement par Philip Wadler). Je suppose que l'origine du terme «position négative» découle de la notion de contravariance («contra» signifie opposé).Il n'est pas autorisé d'avoir le type défini en position négative car on peut écrire des programmes sans terminaison en l'utilisant, c'est-à-dire qu'une forte normalisation échoue en sa présence (plus de détails ci-dessous). Soit dit en passant, c'est la raison du nom de la règle «positivité stricte»: nous n'autorisons pas les positions négatives.
Nous autorisons la deuxième occurrence de
Bad
car elle n'entraîne pas de non-terminaison et nous voulons utiliser le type en cours de définition (Bad
) à un moment donné dans un type de données récursif ( avant la dernière flèche de son constructeur).Il est important de comprendre que la définition suivante ne viole pas la règle stricte de positivité.
La règle s'applique uniquement aux arguments du constructeur (qui sont tous les deux
Good
dans ce cas) et non au constructeur lui-même (voir aussi " Programmation certifiée avec des types dépendants " d' Adam Chlipala ).Un autre exemple violant la positivité stricte:
Vous pourriez également vouloir vérifier cette réponse sur les positions négatives.
Plus d'informations sur la non-résiliation ... La page que vous avez référencée contient quelques explications (ainsi qu'un exemple dans Haskell):
Voici un exemple analogue dans Ocaml, qui montre comment implémenter un comportement récursif sans (!) Utiliser directement la récursivité:
La
nonTerminating
fonction "décompresse" une fonction de son argument et l'applique à l'argument d'origine. Ce qui est important ici, c'est que la plupart des systèmes de types ne permettent pas de passer des fonctions à eux-mêmes, donc un terme commef f
ne vérifie pas le type car il n'y a pas de type pourf
satisfaire le vérificateur de type. L'une des raisons pour lesquelles les systèmes de type ont été introduits est de désactiver l'auto-application (voir ici ).L'encapsulation de types de données comme celui que nous avons présenté ci-dessus peut être utilisée pour contourner ce barrage routier sur le chemin de l'incohérence.
Je veux ajouter que les calculs sans terminaison introduisent des incohérences dans les systèmes logiques. Dans le cas d'Agda et de Coq, le
False
type de données inductif n'a pas de constructeur, vous ne pouvez donc jamais construire un terme de preuve de type False. Mais si des calculs sans terminaison étaient autorisés, on pourrait le faire par exemple de cette façon (en Coq):Ensuite
loop 0
, la vérification de type donneraitloop 0 : False
, donc sous la correspondance Curry-Howard, cela signifierait que nous avons prouvé une fausse proposition.Résultat : la règle de positivité stricte pour les définitions inductives empêche les calculs sans fin qui sont désastreux pour la logique.
la source