Чому в цьому ручному визначеному екземплярі 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)цього, то видаляється неоднозначність, і вона складається добре. Але чому це працює, і чому він не працює інакше?

Відповіді:


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- тип поля в HasFieldобов'язковому ReifiedGetter Person vі rповинен бути Person.
  • Тому що rє Person, тип джерела в HasFieldmust-be Glass Person. Тепер ми можемо вирішити тривіальний Glassyекземпляр для the.
  • Ключ kу HasFieldвидається як буквальний тип: the Symbol name.
  • Перевіряємо передумови екземпляра. Ми знаємо kі r, і вони спільно визначають vчерез HasFieldфункціональну залежність. Екземпляр існує (автоматично генерується для типів записів) і тепер ми знаємо, що vце String. Ми успішно розділили всі типи.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.