Spec-Zone.ru › Haskell 9

6.5.9. Полиморфизм селектора поля записи

Модуль GHC.Records определяет следующее:

class HasField (x :: k) r a | x r -> a where
  getField :: r -> a

Ограничение HasField x r a означает, что x является полем типа a, принадлежащим типу записи r. Метод getField возвращает функцию селектора записи.

Это позволяет создавать определения, которые являются полиморфными по типам записей со специфицированным полем. Например, следующее работает с любым типом записи, который имеет поле name :: String.

foo :: HasField "name" r String => r -> String
foo r = reverse (getField @"name" r)

HasField — это встроенный магический тип класса (аналогичный Coercible, например). Он получает специальное обращение от решателя ограничений (см. Решение ограничений HasField). Пользователи также могут определять свои собственные экземпляры HasField (см. Виртуальные поля записей).

6.5.9.1. Решение ограничений HasField

Если решатель ограничений сталкивается с ограничением HasField x r a, где r — это конкретный тип данных с полем x в области видимости, он автоматически решит ограничение, используя селектор поля в качестве словаря, объединив a с типом поля при необходимости. Это происходит независимо от того, какие расширения включены.

Например, если в области видимости находится следующий тип данных:

data Person = Person { name :: String }

конечный результат будет похож на наличие экземпляра:

instance HasField "name" Person String where
  getField = name

за исключением того, что этот экземпляр фактически нигде не генерируется, а ограничение решается непосредственно решателем ограничений.

Для решения соответствующего ограничения HasField поле должно быть в области видимости. Это сохраняет существующий механизм сокрытия представлений, позволяющий модулю не экспортировать поле, препятствуя доступу и прямому обновлению клиента модуля.

Решение ограничений HasField зависит от функций селекторов полей, которые генерируются для каждого определения типа данных:

  • Если у поля записи нет функции селектора, потому что его тип позволит переменной существования выйти за пределы, соответствующее ограничение HasField не будет решено. Например,

    {-# LANGUAGE ExistentialQuantification #-}
    data Exists t = forall x . MkExists { unExists :: t x }
    

    не порождает селектор unExists :: Exists t -> t x, и мы не будем автоматически решать HasField "unExists" (Exists t) a.

  • Если у поля записи есть полиморфный тип (и, следовательно, функция селектора является высшего ранга), соответствующее ограничение HasField не будет решено, так как это нарушит функциональную зависимость от HasField и/или потребует импредикативности. Например,

    {-# LANGUAGE RankNTypes #-}
    data Higher = MkHigher { unHigher :: forall t . t -> t }
    

    порождает селектор unHigher :: Higher -> (forall t . t -> t), но не приводит к решению ограничения HasField "unHigher" Higher a.

  • У записи GADT может быть ограниченный тип для функции селектора, что может привести к дополнительным объединениям при решении ограничений HasField. Например,

    {-# LANGUAGE GADTs #-}
    data Gadt t where
      MkGadt :: { unGadt :: Maybe v } -> Gadt [v]
    

    порождает селектор unGadt :: Gadt [v] -> Maybe v, поэтому решатель сократит ограничение HasField "unGadt" (Gadt t) b, объединив t ~ [v] и b ~ Maybe v для некоторой свежей метапеременной v, примерно так, как если бы у нас был экземпляр

    instance (t ~ [v], b ~ Maybe v) => HasField "unGadt" (Gadt t) b
    
  • Если тип записи имеет контекст устаревшего типа данных, ограничение HasField будет сокращено до решения ограничений из контекста. Например,

    {-# LANGUAGE DatatypeContexts #-}
    data Eq a => Silly a = MkSilly { unSilly :: a }
    

    порождает селектор unSilly :: Eq a => Silly a -> a, поэтому решатель сократит ограничение HasField "unSilly" (Silly a) b до Eq a (и объединит a с b ), примерно так, как если бы у нас был экземпляр

    instance (Eq a, a ~ b) => HasField "unSilly" (Silly a) b
    

См. Перегруженный запись точкой для применения решения ограничений HasField для реализации «синтаксиса точки записи».

6.5.9.2. Виртуальные поля записей

Пользователи могут определять свои собственные экземпляры HasField, при условии, что они не конфликтуют с поведением встроенного решателя ограничений. Это позволяет определять «виртуальные» поля записей для типов данных, у которых их нет.

Например, этот экземпляр сделает поле name типа Person доступным с помощью #fullname также:

instance HasField "fullname" Person String where
  getField = name

Более существенно, библиотека анонимных записей может предоставить экземпляры HasField для своих анонимных записей и тем самым быть совместимой с полиморфными селекторами записей, введенными в этом предложении. Например, что-то вроде этого позволяет использовать getField для доступа к Record значениям с соответствующей строкой в списке полей на уровне типа:

data Record (xs :: [(k, Type)]) where
  Nil  :: Record '[]
  Cons :: Proxy x -> a -> Record xs -> Record ('(x, a) ': xs)

instance {-# OVERLAPPING #-} HasField x (Record ('(x, a) ': xs)) a where
  getField (Cons _ v _) = v
instance HasField x (Record xs) a => HasField x (Record ('(y, b) ': xs)) a where
  getField (Cons _ _ r) = getField @x r

r :: Record '[ '("name", String) ]
r = Cons Proxy "R" Nil

x = getField @"name" r

Поскольку такие представления могут поддерживать метки полей с другими типами, чем Symbol, класс HasField является поли-типовым (хотя встроенное решение ограничений работает только на уровне типа Symbol). В частности, это позволяет пользователям объявлять метки полей с областью действия, как в следующем примере:

data PersonFields = Name

s :: Record '[ '(Name, String) ]
s = Cons Proxy "S" Nil

y = getField @Name s

Чтобы избежать конфликтов с встроенным решением ограничений, запрещены следующие пользовательские экземпляры HasField (в дополнение к обычным правилам, таким как запрет на появление семейств типов в заголовках экземпляров):

  • HasField _ r _ где r — это переменная;
  • HasField _ (T ...) _ если T — это семейство данных (потому что оно может иметь поля, введенные позже, с помощью объявлений экземпляров данных);
  • HasField x (T ...) _ если x — это переменная, и T имеет какие-либо поля вообще (но этот экземпляр разрешен, если T не имеет полей);
  • HasField "foo" (T ...) _ если T имеет поле foo (но этот экземпляр разрешен, если он не имеет).

Если поле имеет тип высшего ранга или существования, соответствующее ограничение HasField не будет решено автоматически (как описано выше), но в интересах простоты мы не разрешаем пользователям определять свои собственные экземпляры. Если поле не находится в области видимости, соответствующий экземпляр по-прежнему запрещен, чтобы избежать конфликтов в модулях вниз по течению.

© 2002–2007 The University Court of the University of Glasgow. All rights reserved.
Licensed under the Glasgow Haskell Compiler License.
https://downloads.haskell.org/~ghc/9.12.1/docs/users_guide/exts/hasfield.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API