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

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

более конкретно, я ищу две вещи:

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

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

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

Проверьте селектор записи

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

например,

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

функция magic это как

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

возможны ли они с deriving Generic, или мне нужно прибегнуть к шаблону Хаскелл?

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 возвращение [[(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 мы не можем знать является ли функция законным селектором или просто функцией с правильным типом; они неразличимы в Haskell. Невозможно проверить имя "recordId" в magic recordId.