Perché il "trucco dei vincoli" non funziona in questa istanza HasField definita manualmente?


9

Ho questo codice (certamente strano) che utilizza obiettivi e GHC .

{-# LANGUAGE DataKinds, PolyKinds, FlexibleInstances, UndecidableInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
module Main where
import Control.Lens
import GHC.Records 

data Glass r = Glass -- just a dumb proxy

class Glassy r where
  the :: Glass r

instance Glassy x where
  the = Glass

instance (HasField k r v, x ~ r)
-- instance (HasField k r v, Glass x ~ Glass r) 
  => HasField k (Glass x) (ReifiedGetter r v) where
  getField _ = Getter (to (getField @k))

data Person = Person { name :: String, age :: Int } 

main :: IO ()
main = do
  putStrLn $ Person "foo" 0 ^. runGetter (getField @"name" the)

L'idea è avere HasFieldun'istanza che evoca ReifiedGetterun proxy, solo per il gusto di farlo. Ma non funziona:

* Ambiguous type variable `r0' arising from a use of `getField'
  prevents the constraint `(HasField
                              "name"
                              (Glass r0)
                              (ReifiedGetter Person [Char]))' from being solved.

Non capisco perché r0rimanga ambiguo. Ho usato il trucco del vincolo e la mia intuizione è che la testa dell'istanza dovrebbe corrispondere, quindi il typechecker avrebbe trovato r0 ~ Personnelle condizioni preliminari e ciò avrebbe rimosso l'ambiguità.

Se cambio (HasField k r v, x ~ r)in (HasField k r v, Glass x ~ Glass r)questo rimuove l'ambiguità e si compila bene. Ma perché funziona e perché non funziona diversamente?

Risposte:


9

Forse sorprendentemente, ha avuto a che fare con l' Glassessere polivalenti:

*Main> :kind! Glass
Glass :: k -> *

Nel frattempo, a differenza del parametro type di Glass, il "record" in HasFielddeve essere del tipo Type:

*Main> :set -XPolyKinds
*Main> import GHC.Records
*Main GHC.Records> :kind HasField
HasField :: k -> * -> * -> Constraint

Se aggiungo una firma tipo standalone come questa:

{-# LANGUAGE StandaloneKindSignatures #-}
import Data.Kind (Type)
type Glass :: Type -> Type
data Glass r = Glass

quindi controlla i caratteri anche con (HasField k r v, x ~ r).


In effetti, con la firma gentile, il "trucco vincolo" cessa di essere necessario:

instance HasField k r v => HasField k (Glass r) (ReifiedGetter r v) where
  getField _ = Getter (to (getField @k))

main :: IO ()
main = do
  print $ Person "foo" 0 ^. runGetter (getField @"name" the)
  print $ Person "foo" 0 ^. runGetter (getField @"age" the)

Qui, il flusso di informazioni durante il controllo dei caratteri sembra essere:

  • Sappiamo di avere un Person, quindi - attraverso runGetter- il tipo di campo nel campo HasFielddeve essere ReifiedGetter Person ve rdeve essere Person.
  • Perché rè Person, il tipo di origine in HasFielddeve essere Glass Person. Ora possiamo risolvere l' Glassyistanza banale di the.
  • La chiave kin HasFieldè data come un tipo letterale: il Symbol name.
  • Controlliamo le condizioni preliminari dell'istanza. Conosciamo ke rdeterminiamo congiuntamente a vcausa della HasFielddipendenza funzionale. L'istanza esiste (generata automaticamente per i tipi di record) e ora sappiamo che lo vè String. Abbiamo chiarito con chiarezza tutti i tipi.
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.