为什么“约束技巧”在此手动定义的HasField实例中不起作用?


9

我有使用镜头GHC.Records的此代码(很奇怪):

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

这个想法是让一个HasField实例ReifiedGetter从代理服务器中变出,仅仅是为了它的地狱。但它不起作用:

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

我不明白为什么r0仍然模棱两可。我使用了约束技巧,我的直觉是实例头应该匹配,然后类型检查器将r0 ~ Person在前提条件中找到,这将消除歧义。

如果我(HasField k r v, x ~ r)改成 (HasField k r v, Glass x ~ Glass r)这种语言,就可以消除歧义,并且可以编译。但是为什么它起作用,为什么它不起作用呢?

Answers:


9

也许令人惊讶的是,它与Glass种类多变有关:

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

同时,与的类型参数不同Glass,“记录”中HasField的类型必须为Type

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

如果添加这样的独立种类签名:

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

然后即使使用也会进行类型检查(HasField k r v, x ~ r)


实际上,有了亲切的签名,就不再需要“约束技巧”:

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)

在这里,类型检查期间的信息流似乎是:

  • 我们知道我们有一个Person,所以,通过runGetter在-the字段的类型HasField必须ReifiedGetter Person vr必须Person
  • 因为rPerson,所以源类型HasField必须为Glass Person。现在,我们可以解析的琐碎Glassy实例the
  • 中的键kHasField类型文字形式给出:Symbol name
  • 我们检查实例的前提条件。我们知道kr,并且v由于HasField功能依赖性,它们共同确定。该实例存在(自动为记录类型生成),现在我们知道vString。我们已经成功消除了所有类型的歧义。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.