L'équivalence eta pour les fonctions est-elle compatible avec l'opération seq de Haskell?


14

Lemme: En supposant une équivalence éta, nous avons cela (\x -> ⊥) = ⊥ :: A -> B.

Preuve: ⊥ = (\x -> ⊥ x)par eta-équivalence, et (\x -> ⊥ x) = (\x -> ⊥)par réduction sous lambda.

Le rapport Haskell 2010, section 6.2 spécifie la seqfonction par deux équations:

seq :: a -> b -> b
seq ⊥ b = ⊥
seq ab = b, si a ≠ ⊥

Il affirme ensuite "En conséquence, ⊥ n'est pas la même chose que \ x -> ⊥, car seq peut être utilisé pour les distinguer."

Ma question est la suivante: est-ce vraiment une conséquence de la définition de seq?

L'argument implicite semble être que ce seqne serait pas calculable si seq (\x -> ⊥) b = ⊥. Cependant, je n'ai pas été en mesure de prouver qu'un tel seqserait impossible à calculer. Il me semble qu'un tel seqest à la fois monotone et continu, ce qui le place dans le domaine du calculable.

Un algorithme qui implémente tel que seq pourrait fonctionner en essayant d'en chercher xf x ≠ ⊥en énumérant le domaine de fdépart avec ⊥. Bien qu'une telle implémentation, même si possible, devienne assez velue une fois que nous voulons rendre seqpolymorphe.

Y a-t-il une preuve qu'il n'y a pas de calculable seqqui s'identifie (\x -> ⊥)à ⊥ :: A -> B? Sinon, est - il une construction seqqui n'identifie (\x -> ⊥)avec ⊥ :: A -> B?

Réponses:


6

Tout d'abord, soyons explicites sur la façon de seqdistinguer de λ x . :λx.

bottom :: a
bottom = bottom

eta :: a -> b
eta x = bottom

-- This terminates
fortytwo = seq eta 42

-- This does not terminate
infinity = seq bottom 42

C'est donc un fait expérimental que dans Haskell et λ x . se distinguent opérationnellement. Il est également un fait, et un tout à fait évident, qui est calculable parce que Haskell calcule il. Tellement sur Haskell. Vous posez des questions sur le libellé très particulier de la documentation Haskell. Je le lis comme disant que c'est censé satisfaire les deux équations données, mais ces deux équations ne sont pas suffisantes pour la définition de . Voici pourquoi: je peux vous donner deux modèles de (simplement tapé) λλx.seqseqseqλ -calculus dans lesquels seqest calculable et satisfait les équations données, mais dans l'un des modèles et λ x . λx. d'accord, tandis que dans l'autre, ils ne le sont pas.

Dans un modèle simple de domaine théorique où les expressions sont interprétées dans le domaine des fonctions continues [ D E ], nous avons = λ x . , évidemment. Prenez des domaines Scott efficaces ou d'autres pour rendre tout calculable. Il est facile de définir dans un tel modèle.λ[DE]=λx.seq

On peut aussi avoir un modèle de -calcul dans lequel on distingue et λ x . , et alors bien sûr la règle η ne peut pas tenir. Par exemple, nous pouvons le faire en interprétant les fonctions dans le domaine [ D E ] , c'est-à-dire le domaine d'espace de fonction avec un fond supplémentaire attaché. Maintenant, est, eh bien, le bas de [ D E ] , tandis que λ x . est l'élément juste au-dessus. Ils ne peuvent pas être distingués par application, car ilsλseqλx.η[DE][DE]λx. , peu importe à quoi vous les appliquez (ils sont égaux sur le plan de l'extension ). Mais nous avons sequne carte entre les domaines et cela distingue toujours le bas de tous les autres éléments.


1
C'est un fait expérimental qu'en GHC et / ou Hugs ⊥ et λx.⊥. Heureusement, Haskell n'est pas défini par une implémentation. Ma question suggère que Haskell est sous-spécifié en ce qui concerne seq.
Russell O'Connor

Pouvez-vous donner une référence à ce que vous entendez par «domaines Scott efficaces» Cela ne signifie sans doute pas que l'ordre partiel est décidable. De plus, le STLC n'est pas polymorphe, mais Haskell l'est. Habituellement, Haskell est interprété dans le système F ou l'un de ses dérivés. Comment cela affecte-t-il votre argument?
Russell O'Connor du

Section 1.1.4 de mon doctorat dissertation andrej.com/thesis/thesis.pdf a une courte définition des domaines Scott efficaces, et c'est en fait le premier hit de Google qui est disponible gratuitement.
Andrej Bauer

2
Si vous écrivez une preuve pour moi, vous obtiendrez une implémentation de Haskell 98 où la règle eta tient, permettant (foldr (\ ab -> fab) z xs) d'être optimisé en (foldr fz xs) provoquant une augmentation asymptotique des performances de O (n ^ 2) à O (n) (voir ghc.haskell.org/trac/ghc/ticket/7436 ). Plus convaincant, il permettra d'optimiser un NewTypeWrapper dans (NewTypeWrapper. F) sans forcer f à être éta-étendu et d'éviter certaines pénalités de performances asymptotiques actuellement imposées par newTypes dans GHC (dans l'utilisation de foldr par exemple).
Russell O'Connor

1
En fait, vous devez vous assurer que votre compilateur implémente toujours comme . Autrement dit, vous pourriez être tenté de ne pas toujours contracter et donc en principe λ x . et λx.λx. seraient "parfois distinguables", une situation très dangereuse. Pour vous assurer que ce n'est pas le cas, vous devez implémenter seqde manière intelligente, ce qui implique la génération d'un nombre infini de processus, chacun appliquant votre fonction à un élément de base. Si l'un des processus se termine, seqvous pouvez continuer. Il serait intéressant de voir si nous pouvons le faire séquentiellement. Hmm.
Andrej Bauer

2

Notez que la spécification pour seqlaquelle vous citez est pas sa définition. Pour citer le rapport Haskell "La fonction seq est définie par les équations : [et ensuite les équations que vous donnez]".

L'argument suggéré semble être que seq serait non calculable si seq (\ x -> ⊥) b = ⊥.

Un tel comportement violerait la spécification de seq.

Surtout, puisque seq est polymorphe, seqil ne peut pas être défini en termes de déconstructeurs (projections / correspondance de motifs, etc.) sur l'un ou l'autre des deux paramètres.

Existe-t-il une preuve qu'il n'y a pas de séquence calculable qui identifie (\ x -> ⊥) avec ⊥ :: A -> B?

Si seq' (\x -> ⊥) b, on pourrait penser que nous pourrions appliquer le premier paramètre (qui est une fonction) à une valeur, puis sortir ⊥. Mais, seqne peut jamais identifier le premier paramètre avec une valeur de fonction (même s'il se trouve que c'est un pour une certaine utilisation de seq) en raison de son type polymorphe paramétrique. La paramétricité signifie que nous ne savons rien des paramètres. De plus, seqne peut jamais prendre une expression et décider "est-ce ⊥?" (cf. le problème de Halting), seqne peut que tenter de l'évaluer, et diverge lui-même vers ⊥.

Ce qui seqfait est d'évaluer le premier paramètre (pas complètement, mais de "forme normale de tête faible" [1], c'est-à-dire au constructeur le plus haut), puis de retourner le deuxième paramètre. Si le premier paramètre se trouve être (c.-à-d. Un calcul sans terminaison), alors son évaluation entraîne la seqnon-terminaison, et ainsi seq ⊥ a = ⊥.

[1] Free Theorems in the Presence of seq - Johann, Voigtlander http://www.iai.uni-bonn.de/~jv/p76-voigtlaender.pdf


La spécification que je donne pour seq est la définition de seq car c'est exactement ce que dit le rapport Haskell 2010 dans la section 6.2. Votre définition d'opération de seq n'est pas prise en charge par le rapport Haskell 2010: les mots «forme normale de tête» n'apparaissent qu'une seule fois dans le rapport dans un contexte totalement différent. Il est également incompatible avec ma compréhension que GHC réduira souvent le deuxième argument à seq avant le premier argument, ou le premier argument ne sera pas réduit du tout parce que l'analyseur de rigueur a prouvé qu'il n'est pas statiquement inférieur.
Russell O'Connor

La paramétricité ne dit pas directement que nous ne pouvons appliquer aucun déconstructeur, ni que nous ne pouvons jamais identifier le premier paramètre avec une valeur de fonction. Tout parametercity dit pour le calcul lambda polymorphe avec points fixes est que seq peut absorber des fonctions strictes, ou plus généralement certaines relations strictes sont valables pour les termes contenant seq. J'admets qu'il est plausible que la paramétricité puisse être utilisée pour prouver (\ x -> ⊥) & ne; ⊥, mais j'aimerais voir une preuve rigoureuse.
Russell O'Connor

Dans le cas d'une fonction f : forall a . a -> T(où se Ttrouve un autre type), alors fne peut appliquer aucun déconstructeur à son premier argument car il ne sait pas quels déconstructeurs appliquer. Nous ne pouvons pas faire un "cas" sur les types. J'ai essayé d'améliorer la réponse ci-dessus (notamment en citant des informations sur l' seqévaluation de la forme normale de la tête).
dorchard

Je peux essayer de faire la preuve rigoureuse plus tard si je trouve du temps (utiliser des relations dans le style de Reynolds pourrait être une bonne approche).
dorchard

@ RussellO'Connor: la description de seq n'est pas "incohérente" avec ces comportements, c'est juste une spécification opérationnelle (et les comportements sont des optimisations qui ne changent pas le résultat final).
Blaisorblade

2

λx.λx.

Samson Abramsky a étudié ce problème il y a longtemps et a écrit un article intitulé " The Lazy Lambda Calculus ". Donc, si vous voulez des définitions formelles, c'est là que vous pourriez regarder.


1
Apparemment, ces détails ne sont définis qu'en se désengageant dans le "noyau Haskell". Où est-il défini, n'est-ce pas? Le rapport dit, dans la Sec. 1.2 : "Bien que le noyau ne soit pas formellement spécifié, il s'agit essentiellement d'une variante légèrement sucrée du calcul lambda avec une sémantique dénotationnelle simple. La traduction de chaque structure syntaxique dans le noyau est donnée lors de l'introduction de la syntaxe."
Blaisorblade

Le rapport Haskell 2010 dit la même chose , étonnamment.
Blaisorblade

Merci pour la référence à Abramsky! Je l'ai parcouru pour voir comment il répond à la question, et j'ai trouvé la réponse suivante: cstheory.stackexchange.com/a/21732/989
Blaisorblade

2

Prouver que λ x. Ω ‌ ≠ Ω in est l'un des objectifs qu'Abramsky se fixe pour sa théorie du calcul lambda paresseux (page 2 de son article , déjà cité par Uday Reddy), car ils sont tous deux sous une forme normale de tête faible. À partir de la définition 2.7, il discute explicitement que l'éta-réduction λ x. M x → M n'est généralement pas valide, mais cela est possible si M se termine dans tous les environnements. Cela ne signifie pas que M doit être une fonction totale - seulement que l'évaluation de M doit se terminer (en réduisant par exemple à un lambda).

Votre question semble être motivée par des préoccupations pratiques (performances). Cependant, même si le rapport Haskell est loin d’être tout à fait clair, je doute que l’égalisation de λ x. ⊥ ‌avec ⊥ produirait une implémentation utile de Haskell; si elle implémente Haskell '98 ou non est discutable, mais étant donné la remarque, il est clair que les auteurs voulaient que ce soit le cas.

Enfin, comment seq pour générer des éléments pour un type d'entrée arbitraire? (Je sais que QuickCheck définit la classe de types arbitraire pour cela, mais vous n'êtes pas autorisé à ajouter de telles contraintes ici). Cela viole la paramétricité.

Mise à jour : je n'ai pas réussi à coder ce droit (parce que je ne parle pas aussi bien Haskel), et corriger cela semble nécessiter des runSTrégions imbriquées . J'ai essayé d'utiliser une seule cellule de référence (dans la monade ST) pour enregistrer ces éléments arbitraires, les lire plus tard et les rendre universellement disponibles. La paramétricité prouve que break_parametricityci - dessous ne peut pas être défini (sauf en retournant bottom, par exemple une erreur), alors qu'il pourrait récupérer les éléments que votre séquence proposée générerait.

import Control.Monad.ST
import Data.STRef
import Data.Maybe

produce_maybe_a :: Maybe a
produce_maybe_a = runST $ do { cell <- newSTRef Nothing; (\x -> writeSTRef cell (Just x) >> return x) `seq` (readSTRef cell) }

break_parametricity :: a
break_parametricity = fromJust produce_maybe_a

Je dois admettre que je suis un peu flou sur la formalisation de la preuve de paramétrie nécessaire ici, mais cette utilisation informelle de la paramétrie est standard dans Haskell; mais j'ai appris des écrits de Derek Dreyer que la théorie nécessaire s'est rapidement développée au cours de ces dernières années.

MODIFICATIONS:

  • Je ne sais même pas si vous avez besoin de ces extensions, qui sont étudiées pour les langages de type ML, impératifs et non typés, ou si les théories classiques de la paramétricité couvrent Haskell.
  • De plus, j'ai mentionné Derek Dreyer simplement parce que je suis tombé par la suite sur le travail d'Uday Reddy - je ne l'ai appris que récemment grâce à "L'essence de Reynolds". (J'ai seulement commencé à lire de la littérature sur la paramétricité au cours du dernier mois environ).

L'évaluation (\x -> writeSTRef cell (Just x) >> return x)sur des entrées aléatoires n'exécute pas d'écriture dans la cellule. Seules les commandes ST qui entrent dans la séquence passée runSTsont exécutées. De même, l'exécution main = (putStrLn "Hello") `seq` (return ())n'imprime rien sur l'écran.
Russell O'Connor

@ RussellO'Connor, bien sûr, vous avez raison - les tests sont difficiles car seq n'a pas le comportement dont nous discutons. Mais je pense toujours que la génération d'éléments rompt la paramétricité en soi. Je vais essayer de fixer la réponse pour illustrer cela.
Blaisorblade

Hm, la solution évidente à la réponse nécessite l'imbrication des régions runST et l'utilisation de la cellule de la région extérieure dans la région intérieure, mais ce n'est pas autorisé.
Blaisorblade
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.