この手動で定義されたHasFieldインスタンスで「制約トリック」が機能しないのはなぜですか?


9

lensGHC.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)と、あいまいさがなくなり、うまくコンパイルされます。しかし、なぜそれが機能し、なぜそれが逆に機能しないのでしょうか?

回答:


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
  • のでrありPerson、ソースタイプがHasFieldなければなりませんGlass Person。これで、の簡単なGlassyインスタンスを解決できますthe
  • のキーkは、HasFieldタイプリテラルとして指定されSymbol nameます。
  • インスタンスの前提条件を確認します。私たちはとを知ってkおりr、それらvHasField機能的な依存関係のために共同で決定しています。インスタンスが存在します(自動的に記録タイプのために生成される)と、今、私たちはそれを知ってvいますString。すべてのタイプの明確化に成功しました。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.