web-dev-qa-db-fra.com

Techniques de traçage des contraintes

Voici le scénario: j'ai écrit du code avec une signature de type et GHC se plaint de ne pas pouvoir déduire x ~ y pour certains x et y. Vous pouvez généralement jeter un os à GHC et ajouter simplement l'isomorphisme aux contraintes de fonction, mais ceci est une mauvaise idée pour plusieurs raisons:

  1. Cela ne met pas l'accent sur la compréhension du code.
  2. Vous pouvez vous retrouver avec 5 contraintes pour lesquelles on aurait suffi (par exemple, si les 5 sont impliquées par une contrainte plus spécifique)
  3. Vous pouvez vous retrouver avec des contraintes factices si vous avez mal agi ou si GHC est inutile

Je viens de passer plusieurs heures à lutter contre le cas 3. Je joue avec syntactic-2.0 , et j’essayais de définir une version de share indépendante du domaine, similaire au version définie dans NanoFeldspar.hs .

J'ai eu ceci:

{-# LANGUAGE GADTs, FlexibleContexts, TypeOperators #-}
import Data.Syntactic

-- Based on NanoFeldspar.hs
data Let a where
    Let :: Let (a :-> (a -> b) :-> Full b)

share :: (Let :<: sup,
          Domain a ~ sup,
          Domain b ~ sup,
          SyntacticN (a -> (a -> b) -> b) fi) 
      => a -> (a -> b) -> a
share = sugarSym Let

et GHC could not deduce (Internal a) ~ (Internal b), ce qui n’est certainement pas ce que je voulais faire. Donc, soit j'avais écrit un code que je n'avais pas l'intention de créer (ce qui nécessitait la contrainte), soit GHC voulait cette contrainte en raison de certaines autres contraintes que j'avais écrites.

Il s'est avéré que je devais ajouter (Syntactic a, Syntactic b, Syntactic (a->b)) À la liste des contraintes, aucune de celles-ci n'impliquant (Internal a) ~ (Internal b). Je suis essentiellement tombé sur les contraintes correctes; Je n'ai toujours pas de moyen systématique pour les trouver.

Mes questions sont:

  1. Pourquoi GHC a-t-il proposé cette contrainte? Nulle part dans la syntaxe, il n'y a de contrainte Internal a ~ Internal b, Alors d'où GHC a-t-il tiré cela?
  2. En général, quelles techniques peut-on utiliser pour tracer l’origine d’une contrainte dont GHC estime avoir besoin? Même pour les contraintes que je peux découvrir par moi-même, mon approche est essentiellement brutale, forçant le chemin incriminé en écrivant physiquement des contraintes récursives. Cette approche consiste essentiellement à réduire un nombre infini de contraintes et constitue la méthode la moins efficace que je puisse imaginer.
321
crockeea

Tout d'abord, votre fonction a le mauvais type; Je suis à peu près sûr que cela devrait être (sans le contexte) a -> (a -> b) -> b. GHC 7.10 est un peu plus utile pour le signaler, car avec votre code d'origine, il se plaint d'une contrainte manquante Internal (a -> b) ~ (Internal a -> Internal a). Après avoir corrigé le type de share, GHC 7.10 reste utile pour nous guider:

  1. Could not deduce (Internal (a -> b) ~ (Internal a -> Internal b))

  2. Après avoir ajouté ce qui précède, nous obtenons Could not deduce (sup ~ Domain (a -> b))

  3. Après avoir ajouté cela, nous obtenons Could not deduce (Syntactic a), Could not deduce (Syntactic b) et Could not deduce (Syntactic (a -> b))

  4. Après l’ajout de ces trois éléments, il est enfin possible donc on se retrouve avec

    share :: (Let :<: sup,
              Domain a ~ sup,
              Domain b ~ sup,
              Domain (a -> b) ~ sup,
              Internal (a -> b) ~ (Internal a -> Internal b),
              Syntactic a, Syntactic b, Syntactic (a -> b),
              SyntacticN (a -> (a -> b) -> b) fi)
          => a -> (a -> b) -> b
    share = sugarSym Let
    

Donc, je dirais que GHC n'a pas été inutile pour nous guider.

En ce qui concerne votre question sur le traçage d'où GHC tire ses exigences de contrainte, vous pouvez essayer indicateurs de débogage de GHC , en particulier, -ddump-tc-trace, Puis consultez le journal résultant pour voir où Internal (a -> b) ~ t et (Internal a -> Internal a) ~ t sont ajoutés à l'ensemble Wanted, mais la lecture sera assez longue.

5
Cactus