이 수동으로 정의 된 HasField 인스턴스에서“제한 트릭”이 작동하지 않는 이유는 무엇입니까?


9

렌즈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)

그 아이디어는 단지 지옥에서 벗어나는 대리자 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)그 제거 모호성을하고 그것을 잘 컴파일합니다. 그러나 왜 작동하고 다른 방법으로 작동하지 않습니까?

답변:


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 v하며이 r있어야합니다 Person.
  • 때문에 rPerson의에서 소스 유형이 HasField있어야합니다 Glass Person. 이제에 대한 간단한 Glassy인스턴스를 해결할 수 있습니다 the.
  • 주요 k에서이 HasField타입 리터럴로 제공됩니다하십시오 Symbol name.
  • 인스턴스 전제 조건을 확인합니다. 우리는 알고 k있으며 기능적 의존성 때문에 r공동으로 결정 합니다. 인스턴스가 존재하며 (레코드 유형에 대해 자동으로 생성됨) 이제는 입니다 . 우리는 모든 유형을 명확하게 명확하게했습니다.vHasFieldvString
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.