Ответ 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)
.