Occurrences négatives «gardées» dans la définition des types inductifs, toujours mauvaises?


11

Je sais comment certaines occurrences négatives peuvent définitivement être mauvaises:

data False

data Bad a = C (Bad a -> a)

selfApp :: Bad a -> a
selfApp (x@(C x')) = x' x

yc :: (a -> a) -> a
yc f = selfApp $ C (\x -> f (selfApp x))

false :: False
false = yc id

Cependant, je ne sais pas si:

  • tous les types inductifs avec des événements négatifs peuvent tourner mal;

  • dans l'affirmative, il existe un moyen mécanique connu de le faire;

Par exemple, je me suis battu en essayant de faire mal ce type:

type Not a = a -> False

data Bad2 a = C2 (Bad2 (Not a) -> a)

Tout pointeur vers la littérature sur ce sujet serait apprécié.


1
Est-ce Coq? Haskell? Théorie des pseudo-types? Qu'entendez-vous par «se tromper»?
Dave Clarke

@DaveClarke Désolé, le code est Haskell, mais le problème concerne plus les langages comme Coq ou Agda où les occurrences négatives sont interdites. Par "se tromper", je veux dire pouvoir écrire un terme qui diverge, donc pouvoir habiter Faux comme je l'ai fait dans mon exemple à Haskell.
Ptival

Réponses:


10

La raison de l'interdiction des occurrences négatives peut être comprise par analogie avec le théorème de Knaster-Tarski. Ce théorème dit que

si est un réseau complet et f : L L est une fonction monotone sur L , alors l'ensemble des points fixes de f est également un réseau complet. En particulier, il existe un point fixe le plus petit μ f et un point fixe le plus grand ν f .Lf:LLLfμfνf

Dans la théorie des modèles traditionnels, les réseaux peuvent être considérés comme des propositions, et la relation d'ordre p q peut être comprise comme une implication (c'est-à-dire que la vérité de q est impliquée par la vérité de p ).Lpqqp

Lorsque nous passons de la théorie des modèles à la théorie des preuves, les réseaux se généralisent en catégories. Les types peuvent être considérés comme les objets d'une catégorie , et une carte e : P Q représente une preuve que Q peut être dérivée de Q .Ce:PQQQ

Lorsque nous essayons d'interpréter des types définis par des équations récursives, ee, , la chose évidente à faire est de chercher une généralisation du théorème de Knaster-Tarski. Ainsi, au lieu d'une fonction monotone sur un réseau, nous savons que nous voulons unfoncteur F : CC , qui envoie des objets aux objets, mais généralise la condition de monotonie afin que chaque carte e : P Q obtienne une carte F ( e ) : F ( P ) F ( Q ) (avec les conditions de cohérence que F envoie des identités aux identités et préserve les compositions pour que FN=μα.1+α F:CCe:PQF(e):F(P)F(Q)F ).F(gf)=F(g)F(f)

Donc, si vous voulez un type de données inductif , vous devez également fournir une action fonctorielle aux termes de l'opérateur de type F afin de vous assurer que le point fixe souhaité existe. La condition de positivité stricte dans Agda et Coq est uneconditionsyntaxiquequi implique cettecontraintesémantique. En gros, il dit que si vous construisez un opérateur de type à partir de sommes et de produits, vous pouvez toujours faire cuire l'action fonctorielle, et donc tout type formé de cette manière devrait avoir un point fixe.μα.F(α)F

Dans les langages de type dépendant, vous avez également des types indexés et paramétrés, donc votre tâche réelle est plus compliquée. Bob Atkey (qui a blogué à ce sujet ici et ici ) me dit qu'un bon endroit pour chercher l'histoire est:

Comme le note Andrej, fondamentalement, si une occurrence négative est correcte ou non, cela dépend de votre modèle de théorie des types. Fondamentalement, lorsque vous avez une définition récursive, vous recherchez un point fixe, et il y a beaucoup de théorèmes à point fixe en mathématiques.

Personnellement, j'ai beaucoup utilisé le théorème du point fixe de Banach, qui dit que si vous avez une fonction strictement contractuelle sur un espace métrique, alors il a un point fixe unique. Cette idée a été introduite dans la sémantique par (IIRC) Maurice Nivat, et a été largement étudiée par America et Rutten, et a été récemment reliée par Birkedal et ses collaborateurs à une technique opérationnelle populaire appelée "indexation par étapes".

Cela donne lieu à des théories de types où les occurrences négatives dans les types récursifs sont autorisées, mais uniquement lorsque les occurrences négatives se produisent sous un constructeur de type "garde" spécial. Cette idée a été introduite par Hiroshi Nakano, et le lien avec le théorème de Banach a été établi à la fois par moi-même et Nick Benton, ainsi que Lars Birkedal et ses coauteurs.


7

Parfois, vous pouvez résoudre des équations récursives "par chance".

A(A)A.
  1. AA

    AA1.
    1
  2. UNE()1

Conclusion: il existe deux solutions, le type vide (que vous avez appelé False) et le type d'unité ().

UNE(UNE2)2,
data Cow a = Moo ((a -> Bool) -> Bool)

UNE22UNEUNE22UNE

N22N.
2N22NInteger(Integer -> Bool) -> Bool

3

Il est difficile d'ajouter quoi que ce soit aux explications d'Andrej ou de Neel, mais je vais essayer. Je vais essayer d'aborder le point de vue syntaxique, plutôt que de découvrir la sémantique sous-jacente, parce que l'explication est plus élémentaire et que je donnerai une réponse plus simple à votre question.

λ

La référence cruciale est la suivante:

Mendler, N. (1991). Types inductifs et contraintes de type dans le calcul lambda du second ordre. Je n'ai pas trouvé de référence en ligne, je le crains. Les déclarations et les preuves peuvent cependant être trouvées dans la thèse de doctorat de Nax (une lecture fortement recommandée!).

Bune

Bune=BuneUNE

UNE

λX:Bune.X X:BuneUNE

et donc

(λX:Bune.X X) (λX:Bune.X X):UNE

Bune=F(Bune)
F(X)XF(X)

Bien sûr, vous ne travaillez pas avec des types définis équativement mais avec des constructeurs , c'est-à-dire que vous avez

data Bad = Pack (Bad -> A)

plutôt qu'une stricte égalité. Cependant, vous pouvez définir

unpack :: Bad -> (Bad -> A)
unpack (Pack f) = f

ce qui est suffisant pour que ce résultat continue de tenir:

 (\x:Bad -> unpack x x) (Pack (\x:Bad -> unpack x x))

UNE


Dans votre deuxième exemple, les choses sont un peu plus délicates, car vous avez quelque chose comme

Bune=BuneUNE

BuneBuneBune uneBune (Not une)

type Not a = a -> False

avec

data Not a = Not a

Il serait facilement résolu si Haskell permettait de telles définitions de type:

type Acc = Not Acc

Dans ce cas, vous pouvez créer un combinateur en boucle exactement de la même manière qu'auparavant. Je suppose que vous pouvez réaliser une construction similaire (mais plus complexe) en utilisant

data Acc = D (Not Acc)

Le problème ici est que pour construire un isomorphisme

Bad Acc <-> Bad (Not Acc)

vous devez faire face à une variance mixte.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.