Positivité stricte

10

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é.

Pushpa
la source
1
Je ne sais pas pourquoi cela est appelé "négatif", mais il est plus communément connu par l'erreur qu'il produit: débordement de pile :) Ce code peut provoquer une expansion infinie de Ala pile et éventuellement l'exploser (dans les langages basés sur la pile).
wvxvw
cette partie je comprends que vous pourriez écrire des choses arbitraires et donc le calcul sera sans fin. merci
Pushpa
1
Je pense que ce serait une bonne chose de mentionner la non-résiliation dans le corps de votre question. J'ai mis à jour ma réponse en fonction de votre commentaire.
Anton Trunov
@wvxvw Pas nécessairement, il peut simplement fonctionner indéfiniment sans faire exploser la pile, à condition que le compilateur implémente la récursivité de la queue, par exemple mon exemple dans OCaml ci-dessous n'explose pas la pile.
Anton Trunov
1
@AntonTrunov bien sûr, c'était plus un jeu de mots sur le nom du site plutôt qu'une tentative d'être précis.
wvxvw

Réponses:

17

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 ".UNEBUNEB

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.TTT

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 cc'est un constructeur pour un type de données T:

  • si cest une constante, nous pouvons la considérer comme une fonction unit -> T, ou de manière équivalente (empty -> T) -> T,
  • si cest unaire, nous pouvons le considérer comme une fonction T -> T, ou de manière équivalente (unit -> T) -> T,
  • si cest binaire, nous pouvons le considérer comme une fonction T -> T -> T, ou de manière équivalente T * T -> T, ou équivalente (bool -> T) -> T,
  • si nous voulions un constructeur cqui prend sept arguments, nous pourrions le voir comme une fonction (seven -> T) -> Toù se seventrouve un type précédemment défini avec sept éléments.
  • nous pouvons également avoir un constructeur cqui 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

c : (A -> T) -> T

où nous appelons Al' arité de cet nous pensons ccomme un constructeur qui prend A-nombreux arguments de type Tpour produire un élément de T.

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 constructeur

broken: (T -> T) -> T

puis la question "combien d'arguments brokenfaut-il?" n'a pas de bonne réponse. Vous pouvez essayer d'y répondre avec "il faut T-de nombreux arguments", mais cela ne fonctionnera pas, car il Tn'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 type Tet une fonction injective (T -> T) -> T, et réussirions, mais nous briserions également le principe d'induction pour le Tlong du chemin. Donc, c'est juste une mauvaise idée d'essayer une telle chose.

λvλvcB

c : B * (A -> T) -> T

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 Ade dépendre de B:

c : (∑ (x : B), A x -> T) -> T

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 deux

d' : (∑ (x : B'), A' x -> T) -> T
d'' : (∑ (x : B''), A'' x -> T) -> T

alors nous pouvons les combiner en un seul

d : (∑ (x : B), A x -> T) -> T

B := B' + B''
A(inl x) := A' x
A(inr x) := A'' x

Soit dit en passant, si nous curry la forme générale, nous voyons qu'elle est équivalente à

c : ∏ (x : B), ((A x -> T) -> T)

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!).

Andrej Bauer
la source
1
Merci encore Andrej après mon déjeuner, ce sera la chose la plus difficile à digérer pour moi. À votre santé.
Pushpa
9

La première occurrence de Badest 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 Badcar 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é.

data Good : Set where
  good : Good → Good → Good

La règle s'applique uniquement aux arguments du constructeur (qui sont tous les deux Gooddans 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:

data Strange : Set where
  strange : ((Bool → Strange) → (ℕ → Strange)) → Strange
                       ^^     ^
            this Strange is   ...this arrow
            to the left of... 

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):

Les déclarations non strictement positives sont rejetées car on peut écrire une fonction non terminale en les utilisant. Pour voir comment on peut écrire une définition de boucle en utilisant le type de données Bad ci-dessus, voir BadInHaskell .

Voici un exemple analogue dans Ocaml, qui montre comment implémenter un comportement récursif sans (!) Utiliser directement la récursivité:

type boxed_fun =
  | Box of (boxed_fun -> boxed_fun)

(* (!) in Ocaml the 'let' construct does not permit recursion;
   one have to use the 'let rec' construct to bring 
   the name of the function under definition into scope
*)
let nonTerminating (bf:boxed_fun) : boxed_fun =
  match bf with
    Box f -> f bf

let loop = nonTerminating (Box nonTerminating)

La nonTerminatingfonction "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 comme f fne vérifie pas le type car il n'y a pas de type pour fsatisfaire 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 Falsetype 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):

Fixpoint loop (n : nat) : False = loop n

Ensuite loop 0, la vérification de type donnerait loop 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.

Anton Trunov
la source
Maintenant, je suis confus. Données spécifiques Bon: Définissez où bon: Bon → Bon →. Nous allons essayer de comprendre et de revenir dans une heure /
Pushpa
La règle ne s'applique pas au constructeur lui-même, seulement à ses arguments, c'est-à-dire que les flèches au niveau supérieur d'une définition de constructeur n'ont pas d'importance. J'ai également ajouté un autre exemple de violation (indirect).
Anton Trunov