Voici le scénario: j'ai écrit du code avec une signature de type et GHC ne peut pas déduire x ~ y pour certains x
et y
. Vous pouvez généralement lancer un os GHC et simplement ajouter l'isomorphisme aux contraintes de fonction, mais c'est une mauvaise idée pour plusieurs raisons:
- Il ne met pas l'accent sur la compréhension du code.
- Vous pouvez vous retrouver avec 5 contraintes là où une aurait suffi (par exemple, si les 5 sont impliquées par une contrainte plus spécifique)
- Vous pouvez vous retrouver avec de fausses contraintes si vous avez fait quelque chose de mal ou si GHC n'est pas utile
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 indépendante de domaine de share
, similaire à la version définie dans NanoFeldspar.hs
.
J'avais 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 recherchais. Donc, soit j'avais écrit du code que je n'avais pas l'intention de faire (ce qui nécessitait la contrainte), soit GHC voulait cette contrainte en raison d'autres contraintes que j'avais écrites.
Il s'avère que je devais ajouter (Syntactic a, Syntactic b, Syntactic (a->b))
à la liste des contraintes, ce qui n'implique rien (Internal a) ~ (Internal b)
. Je suis essentiellement tombé sur les bonnes contraintes; Je n'ai toujours pas de moyen systématique de les trouver.
Mes questions sont:
- 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ù vient le GHC? - En général, quelles techniques peuvent être utilisées pour retracer l'origine d'une contrainte dont GHC pense avoir besoin? Même pour les contraintes que je peux découvrir moi-même, mon approche consiste essentiellement à forcer brutalement le chemin offensant en écrivant physiquement des contraintes récursives. Cette approche consiste à descendre un trou infini de contraintes de lapin et est la méthode la moins efficace que j'imagine.
a
et b
sont liés - regardez la signature de type en dehors de votre contexte - a -> (a -> b) -> a
, non a -> (a -> b) -> b
. C'est peut-être ça? Avec les solveurs de contraintes, ils peuvent influer sur l'égalité transitive n'importe où , mais les erreurs indiquent généralement un emplacement "proche" de l'endroit où la contrainte a été induite. Ce serait cool si @jozefg - peut-être annoter des contraintes avec des balises ou quelque chose, pour montrer d'où elles viennent? : s