Mengapa "trik kendala" tidak berfungsi dalam instance HasField yang ditentukan secara manual ini?

9

Saya memiliki kode ini (memang aneh) yang menggunakan lensa dan GHC.Rekam :

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

Idenya adalah memiliki HasFieldcontoh yang memunculkan ReifiedGetterkeluar dari proxy, hanya untuk itu. Tapi itu tidak berfungsi:

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

Saya tidak mengerti mengapa r0tetap ambigu. Saya menggunakan trik kendala , dan intuisi saya adalah bahwa head instance harus cocok, maka typechecker akan menemukan r0 ~ Persondalam prasyarat, dan itu akan menghapus ambiguitas.

Jika saya berubah (HasField k r v, x ~ r)menjadi (HasField k r v, Glass x ~ Glass r)yang menghapus ambiguitas dan mengkompilasi dengan baik. Tetapi mengapa itu berhasil, dan mengapa itu tidak bekerja sebaliknya?

danidiaz
sumber

Jawaban:

9

Mungkin mengejutkan, itu ada hubungannya dengan Glassmenjadi poli-kind:

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

Sementara itu, tidak seperti parameter tipe Glass, "record" di HasFieldharus dari jenis Type:

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

Jika saya menambahkan tanda tangan jenis mandiri seperti ini:

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

maka itu ketik pemeriksaan bahkan dengan (HasField k r v, x ~ r).


Bahkan, dengan tanda tangan yang baik, "trik kendala" tidak lagi diperlukan:

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)

Di sini, aliran informasi selama pemeriksaan ketik tampaknya:

  • Kami tahu kami memiliki Person, jadi — melalui runGetter— jenis bidang dalam HasFieldmust be ReifiedGetter Person vdan rmust be Person.
  • Karena ritu Person, jenis sumber di HasFieldharus Glass Person. Kita sekarang dapat menyelesaikan Glassycontoh sepele untuk the.
  • Kunci kdalam HasFielddiberikan sebagai tipe literal: the Symbol name.
  • Kami memeriksa prasyarat contoh. Kami tahu kdan r, dan mereka bersama-sama menentukanv karena HasFieldketergantungan fungsional. Contoh ada (otomatis dihasilkan untuk jenis catatan) dan sekarang kita tahu bahwa vadalah String. Kami telah berhasil membuat semua jenis ambigu.
danidiaz
sumber