Можно ли перечислить имена и типы полей в типе данных записи, который получает Generic?

Я знаю, что для типов данных, которые выводят Data.Data, constrFields дает список имен полей. Глядя на документацию GHC.Generics, я думаю, что то же самое должно быть возможно и для Generic. (но, к сожалению, не смог понять, как это сделать сам).

В частности, я ищу две вещи:

Список всех полей записи

... в рамках программы Haskell. Я знал, что aeson способен автоматически выводить JSON-представление любого типа данных записи, которое выводит Generic, но только чтение его исходного кода подтвердил, что я здесь невежественный. Из того, что я могу догадаться, aeson должен иметь возможность получить все имена полей (как String или ByteString s) из типа данных записи, а также их типы (которые имеют тип что-то вроде TypeRep в Data.Typeable или экземпляр Eq: все, что можно использовать для сопоставления блоков case).

Я смутно предполагаю, что создание класса и экземпляров для M1, :*: и т.д. является способом, но я не мог сделать это с помощью проверки типов.

Осмотрите селектор записей

Чтобы получить тип данных записи, к которому он принадлежит, имя поля записи (как String) и т.д.

Например, данный

data Record = Record
    { recordId :: Int32
    , recordName :: ByteString
    } deriving Generic

Функция magic, которая похожа на

typeOf (Record {}) == typeOf (magic recordId)

Возможно ли это с помощью deriving Generic, или мне нужно обратиться к Template Haskell?

Ответ 1

Список всех полей записи

Это очень возможно, и это действительно делается путем повторения структуры Rep с использованием класса. Приведенное ниже решение работает для типов с одним конструктором и возвращает пустые имена строк для полей без селекторов:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}

import Data.ByteString (ByteString)
import Data.Data
import Data.Int
import Data.Proxy
import GHC.Generics
import qualified Data.ByteString as B

data Record = Record { recordId :: Int32, recordName :: ByteString }
  deriving (Generic)

class Selectors rep where
  selectors :: Proxy rep -> [(String, TypeRep)]

instance Selectors f => Selectors (M1 D x f) where
  selectors _ = selectors (Proxy :: Proxy f)

instance Selectors f => Selectors (M1 C x f) where
  selectors _ = selectors (Proxy :: Proxy f)

instance (Selector s, Typeable t) => Selectors (M1 S s (K1 R t)) where
  selectors _ =
    [ ( selName (undefined :: M1 S s (K1 R t) ()) , typeOf (undefined :: t) ) ]

instance (Selectors a, Selectors b) => Selectors (a :*: b) where
  selectors _ = selectors (Proxy :: Proxy a) ++ selectors (Proxy :: Proxy b)

instance Selectors U1 where
  selectors _ = []

Теперь мы можем иметь:

selectors (Proxy :: Proxy (Rep Record))
-- [("recordId",Int32),("recordName",ByteString)]

Наименее очевидная часть здесь - это selName и Selector: этот класс можно найти в GHC.Generics, и он позволяет нам извлекать имена селекторов из сгенерированных типов селекторов. В случае Record представление

:kind! Rep Record
Rep Record :: * -> *
= D1
    Main.D1Record
    (C1
       Main.C1_0Record
       (S1 Main.S1_0_0Record (Rec0 Int32)
        :*: S1 Main.S1_0_1Record (Rec0 ByteString)))

и типы селекторов: Main.S1_0_0Record и Main.S1_0_1Record. Мы можем получить доступ к этим типам только путем извлечения их из типа Rep с использованием классов или семейств типов, потому что GHC не экспортирует их. В любом случае, selName получает нам имя селектора из любого узла M1 с тегом селектора s (он имеет более общий тип t s f a -> String, но это нас здесь не касается).

Также возможно обрабатывать несколько конструкторов и иметь selectors return [[(String, TypeRep)]]. В этом случае у нас, вероятно, будет два класса, один из которых аналогичен указанному выше, используется для извлечения селекторов из данного конструктора, а другой класс для сбора списков для конструкторов.

Осмотреть селектор записей

Легко получить тип записи из функции:

class Magic f where
  magic :: f -> TypeRep

instance Typeable a => Magic (a -> b) where
  magic _ = typeOf (undefined :: a)

Или статически:

type family Arg f where
   Arg (a -> b) = a

Однако без TH мы не можем знать, является ли функция законным селектором или просто функцией с правильным типом; они неразличимы в Хаскеле. В magic recordId нет способа проверить имя "recordId".


Обновление 2019: извлечение селектора с помощью GHC 8.6.5 и напечатано TypeRep s. Мы немного модернизируем решение, избавляясь от прокси в пользу типовых приложений.

{-# language
  AllowAmbiguousTypes,
  DeriveGeneric,
  FlexibleContexts,
  FlexibleInstances,
  RankNTypes,
  TypeApplications,
  TypeInType
  #-}

import Type.Reflection
import GHC.Generics

class Selectors rep where
  selectors :: [(String, SomeTypeRep)]

instance Selectors f => Selectors (M1 D x f) where
  selectors = selectors @f

instance Selectors f => Selectors (M1 C x f) where
  selectors = selectors @f

instance (Selector s, Typeable t) => Selectors (M1 S s (K1 R t)) where
  selectors =
    [(selName (undefined :: M1 S s (K1 R t) ()) , SomeTypeRep (typeRep @t))]

instance (Selectors a, Selectors b) => Selectors (a :*: b) where
  selectors = selectors @a ++ selectors @b

instance Selectors U1 where
  selectors = []

Теперь использование становится selectors @(Rep MyType).