Techniken zum Verfolgen von Einschränkungen

322

Hier ist das Szenario: Ich habe Code mit einer Typensignatur geschrieben und GHC-Beschwerden konnten für einige xund nicht auf x ~ y schließen y. Normalerweise können Sie GHC einen Knochen werfen und einfach den Isomorphismus zu den Funktionseinschränkungen hinzufügen. Dies ist jedoch aus mehreren Gründen eine schlechte Idee:

  1. Das Verständnis des Codes wird nicht betont.
  2. Sie können mit 5 Einschränkungen enden, bei denen eine ausreichend gewesen wäre (z. B. wenn die 5 durch eine spezifischere Einschränkung impliziert werden).
  3. Sie können mit falschen Einschränkungen enden, wenn Sie etwas falsch gemacht haben oder wenn GHC nicht hilfreich ist

Ich habe gerade einige Stunden mit Fall 3 gekämpft. Ich spiele mit syntactic-2.0und habe versucht, eine domänenunabhängige Version von zu definieren share, ähnlich der in definierten Version NanoFeldspar.hs.

Ich hatte das:

{-# 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

und GHC could not deduce (Internal a) ~ (Internal b), was ich sicherlich nicht wollte. Entweder hatte ich Code geschrieben, den ich nicht beabsichtigt hatte (für den die Einschränkung erforderlich war), oder GHC wollte diese Einschränkung aufgrund einiger anderer Einschränkungen, die ich geschrieben hatte.

Es stellte sich heraus, dass ich zur (Syntactic a, Syntactic b, Syntactic (a->b))Einschränkungsliste hinzufügen musste , von denen keine impliziert (Internal a) ~ (Internal b). Ich bin im Grunde auf die richtigen Einschränkungen gestoßen; Ich habe immer noch keinen systematischen Weg, sie zu finden.

Meine Fragen sind:

  1. Warum hat GHC diese Einschränkung vorgeschlagen? Nirgendwo in der Syntaktik gibt es eine Einschränkung Internal a ~ Internal b. Woher hat GHC diese?
  2. Welche Techniken können im Allgemeinen verwendet werden, um den Ursprung einer Einschränkung zu verfolgen, die GHC für erforderlich hält? Selbst für Einschränkungen, die ich selbst entdecken kann, ist mein Ansatz im Wesentlichen brutal, den beleidigenden Pfad zu erzwingen, indem ich rekursive Einschränkungen physisch aufschreibe. Dieser Ansatz geht im Grunde genommen durch ein unendliches Kaninchenloch von Zwängen und ist ungefähr die am wenigsten effiziente Methode, die ich mir vorstellen kann.
Crockeea
quelle
21
Es gab einige Diskussionen über einen Debugger auf Typebene, aber der allgemeine Konsens scheint zu zeigen, dass die interne Logik des Typecheckers nicht helfen wird: / Ab sofort ist Haskells Constraint Solver eine beschissene undurchsichtige Logiksprache :)
Daniel Gratzer
12
@jozefg Hast du einen Link für diese Diskussion?
Crockeea
36
Oft ist es hilfreich, die Typensignatur einfach vollständig zu entfernen und sich von ghci sagen zu lassen, wie die Signatur aussehen soll.
Tobias Brandt
12
Irgendwie aund bgebunden - schauen Sie sich die Typensignatur außerhalb Ihres Kontexts an - a -> (a -> b) -> anicht a -> (a -> b) -> b. Vielleicht ist es das? Mit Constraint-Solvern können sie die transitive Gleichheit überall beeinflussen , aber die Fehler zeigen normalerweise einen Ort "nahe" an dem Ort, an dem die Constraint induziert wurde. Das wäre aber cool, @jozefg - vielleicht Einschränkungen mit Tags oder so etwas zu kommentieren, um zu zeigen, woher sie kommen? : s
Athan Clark

Antworten:

6

Zuallererst hat Ihre Funktion den falschen Typ; Ich bin mir ziemlich sicher, dass es so sein sollte (ohne den Kontext) a -> (a -> b) -> b. GHC 7.10 ist etwas hilfreicher, um darauf hinzuweisen, da es sich bei Ihrem ursprünglichen Code über eine fehlende Einschränkung beschwert Internal (a -> b) ~ (Internal a -> Internal a). Nach der Korrektur sharedes Typs bleibt GHC 7.10 hilfreich, um uns zu führen:

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

  2. Nachdem wir das oben Gesagte hinzugefügt haben, erhalten wir Could not deduce (sup ~ Domain (a -> b))

  3. Danach Zugabe, bekommen wir Could not deduce (Syntactic a), Could not deduce (Syntactic b)undCould not deduce (Syntactic (a -> b))

  4. Nach dem Hinzufügen dieser drei wird es schließlich typechecks; so enden wir mit

    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

Ich würde also sagen, dass GHC uns nicht nutzlos geführt hat.

Bei Ihrer Frage zum Verfolgen, woher GHC seine Einschränkungsanforderungen bezieht, können Sie insbesondere die Debugging-Flags von GHC ausprobieren -ddump-tc-traceund dann das resultierende Protokoll durchlesen, um festzustellen, wo Internal (a -> b) ~ tund (Internal a -> Internal a) ~ tzu dem WantedSatz hinzugefügt werden. Dies ist jedoch ein ziemlich langer Lesevorgang .

Kaktus
quelle
0

Haben Sie dies in GHC 8.8+ versucht?

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

Der Schlüssel besteht darin, Typloch unter Einschränkungen zu verwenden: _ => your difficult type

Michal Gajda
quelle