Tecniche per tracciare vincoli


322

Ecco lo scenario: ho scritto del codice con una firma del tipo e i reclami di GHC non sono stati in grado di dedurre x ~ y per alcuni xe y. Di solito puoi lanciare GHC in un osso e semplicemente aggiungere l'isomorfismo ai vincoli di funzione, ma questa è una cattiva idea per diversi motivi:

  1. Non enfatizza la comprensione del codice.
  2. Puoi finire con 5 vincoli in cui uno sarebbe stato sufficiente (ad esempio, se i 5 sono implicati da un altro vincolo specifico)
  3. Puoi finire con vincoli fasulli se hai fatto qualcosa di sbagliato o se GHC non aiuta

Ho appena trascorso diverse ore a combattere il caso 3. Sto giocando syntactic-2.0, e stavo cercando di definire una versione indipendente dal dominio share, simile alla versione definita in NanoFeldspar.hs.

Ho avuto questo:

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

e GHC could not deduce (Internal a) ~ (Internal b), che non è certo quello che stavo cercando. Quindi o avevo scritto un codice che non intendevo (che richiedeva il vincolo), oppure GHC voleva quel vincolo a causa di altri vincoli che avevo scritto.

Si scopre che dovevo aggiungere (Syntactic a, Syntactic b, Syntactic (a->b))all'elenco dei vincoli, nessuno dei quali implica (Internal a) ~ (Internal b). Fondamentalmente mi sono imbattuto nei vincoli corretti; Non ho ancora un modo sistematico per trovarli.

Le mie domande sono:

  1. Perché GHC ha proposto questo vincolo? Da nessuna parte in sintattica c'è un vincolo Internal a ~ Internal b, quindi da dove GHC l'ha tirato fuori?
  2. In generale, quali tecniche possono essere utilizzate per rintracciare l'origine di un vincolo di cui GHC ritiene di aver bisogno? Anche per i vincoli che posso scoprire da solo, il mio approccio è essenzialmente brutale forzando il percorso offensivo scrivendo fisicamente vincoli ricorsivi. Questo approccio sta fondamentalmente scendendo in una buca di coniglio infinita di vincoli e riguarda il metodo meno efficiente che posso immaginare.

21
Ci sono state alcune discussioni su un debugger a livello di tipo, ma il consenso generale sembra mostrare che la logica interna del typechecker non sarà di aiuto: / A partire da ora il risolutore di vincoli di Haskell è un linguaggio logico opaco osceno :)
Daniel Gratzer

12
@jozefg Hai un link per quella discussione?
crockeea,

36
Spesso aiuta a rimuovere completamente la firma del tipo e lasciare che ghci ti dica cosa pensa che dovrebbe essere la firma.
Tobias Brandt,

12
In qualche modo ae bsono vincolati - guarda la firma del tipo al di fuori del tuo contesto - a -> (a -> b) -> a, no a -> (a -> b) -> b. Forse è tutto? Con i risolutori di vincoli, possono influenzare l'uguaglianza transitiva ovunque , ma gli errori di solito mostrano una posizione "vicina" a dove è stato indotto il vincolo. Sarebbe bello però @jozefg - forse annotare vincoli con tag o qualcosa del genere, per mostrare da dove provengono? : s
Athan Clark,

Risposte:


6

Prima di tutto, la tua funzione ha il tipo sbagliato; Sono abbastanza sicuro che dovrebbe essere (senza il contesto) a -> (a -> b) -> b. GHC 7.10 è un po 'più utile per evidenziarlo, perché con il codice originale si lamenta di un vincolo mancante Internal (a -> b) ~ (Internal a -> Internal a). Dopo aver corretto shareil tipo, GHC 7.10 rimane utile nel guidarci:

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

  2. Dopo aver aggiunto quanto sopra, otteniamo Could not deduce (sup ~ Domain (a -> b))

  3. Dopo aver aggiunto che, otteniamo Could not deduce (Syntactic a), Could not deduce (Syntactic b)eCould not deduce (Syntactic (a -> b))

  4. Dopo aver aggiunto questi tre, finalmente controlla i caratteri; così finiamo con

    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
    

Quindi direi che GHC non è stato inutile nel guidarci.

Per quanto riguarda la tua domanda sulla traccia da cui GHC ottiene i suoi requisiti di vincolo, potresti provare i flag di debug di GHC , in particolare -ddump-tc-trace, e quindi leggere il registro risultante per vedere dove Internal (a -> b) ~ te (Internal a -> Internal a) ~ tvengono aggiunti al Wantedset, ma sarà una lettura piuttosto lunga .


0

Hai provato questo in GHC 8.8+?

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

La chiave è usare il foro di tipo tra i vincoli: _ => your difficult type

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.