Функции с высшими видами?
Предположим, определены следующие типы данных:
data X a = X {getX :: a}
data Y a = Y {getY :: a}
data Z a = Z {getZ :: a}
Должно быть три отдельных функции, getX
, getY
, а также getZ
? Мне кажется, что может быть функция, определенная примерно так:
get :: forall (τ :: (* -> *)) (a :: *). τ a -> a
get (_ x) = x
Очевидно, что это недопустимый стандарт Haskell, но существует так много расширений для GHC, которые, кажется, могут иметь решение (RankNTypes
,ExistentialQuantification
,DataKinds
,так далее.). Помимо простой причины избегать небольшого количества печатания, есть преимущество предотвращения загрязнения пространства имен, которое создает решение для записи. Я полагаю, что это на самом деле более неявное решение, чем использование класса типа, подобного следующему:
class Get f where
get :: f a -> a
Однако кажется, что определение универсальной функции было бы более полезным, чем класс типов, потому что тот факт, что она неявно определена, означает, что ее можно использовать во многих других местах, так же, как ($)
или же (.)
используется. Итак, мой вопрос состоит из трех частей: есть ли способ сделать это, хорошая идея, и если нет, то какой путь лучше?
2 ответа
Как насчет этого типа?
newtype Pred a = Pred (a -> Bool)
Или этот?
data Proxy a = Proxy
Там нет никакого способа, чтобы получить a
из Pred a
, Вы можете только поставить a
s в. Аналогично, нет никакого способа получить a
из Proxy a
потому что нет a
внутри.
Так что функция get :: forall f a. f a -> a
не может существовать вообще. Вам нужно использовать класс типов, чтобы различать эти типы f
из которого вы можете извлечь a
и те, из которых вы не можете.
Ну, этот неограниченный родовой тип get
конечно не может работать. Это также позволит вам извлечь, скажем, Void
значение от Const () :: Const () Void
,
Однако вы можете получить достаточно ограниченную версию этой функции с помощью обобщений. Вам все еще нужен класс типов, но не нужно определять экземпляры в традиционном смысле. В конечном итоге это выглядит так:
{-# LANGUAGE TypeFamilies, DeriveGeneric, DeriveAnyClass #-}
import GHC.Generics
class Get τ where
get :: τ a -> a
data X a = X a deriving (Generic1, Get)
data Y a = Y a deriving (Generic1, Get)
data Z a = Z a deriving (Generic1, Get)
Чтобы реально заставить это работать, нам нужны только два странных экземпляра типа представления:
instance Get f => Get (M1 i t f) where get = get . unM1
instance Get Par1 where get = unPar1
Теперь актуальная реализация для X
, Y
а также Z
можно просто использовать сигнатуру по умолчанию и сократить извлечение до базового представления типа. Для этого определите класс следующим образом:
{-# LANGUAGE DefaultSignatures #-}
class Get τ where
get :: τ a -> a
default get :: (Generic1 τ, Get (Rep1 τ)) => τ a -> a
get = get . from1