#generics #haskell
#дженерики #haskell
Вопрос:
Я ищу простой пример, касающийся использования функции selName GHC Haskell.Пакет Generics.
Учитывая следующий тип записи:
{-# language DeriveGeneric #-}
data Person = Person {
firstName :: String
, lastName :: String
, age :: Integer
} deriving(Generic
Как будет использоваться функция selName для получения имени селектора FirstName?
Ответ №1:
Приведенный ниже код требует следующих расширений:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeFamilies #-}
Во-первых, вы можете использовать GHCi, чтобы узнать общее представление вашего типа person:
λ> :kind! Rep Person ()
Rep Person () :: *
= M1
D
Main.D1Person
(M1
C
Main.C1_0Person
(M1 S Main.S1_0_0Person (K1 R [Char])
:*: (M1 S Main.S1_0_1Person (K1 R [Char])
:*: M1 S Main.S1_0_2Person (K1 R Integer))))
()
Тип селектора, который вы ищете, это Main.S1_0_1Person
. Чтобы извлечь это, вы можете использовать семейство типов:
type family FirstSelector (f :: * -> *) :: *
type instance FirstSelector (M1 D x f) = FirstSelector f
type instance FirstSelector (M1 C x f) = FirstSelector f
type instance FirstSelector (a :*: b) = FirstSelector a -- Choose first selector
type instance FirstSelector (M1 S s f) = s
-- Note: this doesn't support types with multiple constructors.
-- You'll get a type error in that case.
Нам нужен способ передать Person
тип нашей функции, которая получает имя первого селектора. Для этого мы можем использовать Proxy
тип, который имеет только один конструктор, но «помечен» типом: (вы также можете использовать аргумент undefined :: Person
и игнорировать его, но таким образом гарантируется, что вы можете игнорировать только его).
data Proxy a = Proxy -- also provided by the `tagged` hackage package
Теперь тип selName
is selName :: t s (f :: * -> *) a -> [Char]
, поэтому для использования функции вам нужен тип, соответствующий шаблону t s (f :: * -> *) a
. Мы используем тот же трюк для создания a SelectorProxy
, который имеет только один конструктор, но имеет требуемую форму:
data SelectorProxy s (f :: * -> *) a = SelectorProxy
type SelectorProxy' s = SelectorProxy s Proxy ()
Наконец, мы готовы написать функцию, которая получает имя селектора:
firstSelectorName :: forall a. (Generic a, Selector (FirstSelector (Rep a))) => Proxy a -> String
firstSelectorName Proxy = selName (SelectorProxy :: SelectorProxy' (FirstSelector (Rep a)))
И если вы загрузите это в GHCi, вы увидите, что это работает:
λ> firstSelectorName (Proxy :: Proxy Person)
"firstName"
Комментарии:
1. Вау! Не так просто, как я думал. Большое вам спасибо за этот подробный пример, он поможет мне (и, надеюсь, многим другим!) Разобраться в GHC. Дженерики!
2. Поскольку это очень одобренный ответ, возможно, имеет смысл обновить эту информацию с помощью последней разработки в GHC или упомянуть, по-прежнему ли это лучший способ сделать это