Warum funktioniert der "Constraint-Trick" in dieser manuell definierten HasField-Instanz nicht?

9

Ich habe diesen (zugegebenermaßen seltsamen) Code, der Objektiv und GHC verwendet. Aufzeichnungen :

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

Die Idee ist, eine HasFieldInstanz zu haben, die ReifiedGetters aus einem Proxy zaubert , nur zum Teufel. Aber es funktioniert nicht:

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

Ich verstehe nicht, warum r0zweideutig bleibt. Ich habe den Constraint-Trick verwendet , und meine Intuition ist, dass der Instanzkopf übereinstimmen sollte, dann würde der Typechecker r0 ~ Personin den Voraussetzungen finden, und das würde die Mehrdeutigkeit beseitigen.

Wenn ich ändern (HasField k r v, x ~ r)in , (HasField k r v, Glass x ~ Glass r)dass wird die Mehrdeutigkeit und kompiliert es in Ordnung. Aber warum funktioniert es und warum funktioniert es nicht andersherum?

danidiaz
quelle

Antworten:

9

Vielleicht überraschend hatte es damit zu tun Glass, poly-kinded zu sein:

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

Im Gegensatz zum Typparameter von Glassmuss der "Datensatz" in HasFieldder Art sein Type:

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

Wenn ich eine eigenständige Signatur wie diese hinzufüge:

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

dann prüft es sogar mit (HasField k r v, x ~ r).


Tatsächlich hört der "Einschränkungstrick" mit der Art Signatur auf, notwendig zu sein:

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)

Hier scheint der Informationsfluss während der Typüberprüfung zu sein:

  • Wir wissen, dass wir einen haben Person, also - durch runGetter- den Feldtyp im HasFieldMuss sein ReifiedGetter Person vund das rMuss sein Person.
  • Weil rist Person, muss der Quelltyp in der HasFieldsein Glass Person. Wir können jetzt die triviale GlassyInstanz für die auflösen the.
  • Der Schlüssel kin der HasFieldwird als Typliteral angegeben: the Symbol name.
  • Wir überprüfen die Instanzvoraussetzungen. Wir wissen kund r, und sie bestimmen gemeinsam vaufgrund der HasFieldfunktionalen Abhängigkeit. Die Instanz existiert (automatisch für Datensatztypen generiert) und jetzt wissen wir, dass dies der Fall vist String. Wir haben alle Typen erfolgreich disambiguiert.
danidiaz
quelle