Функции с высшими видами?

Предположим, определены следующие типы данных:

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, Вы можете только поставить as в. Аналогично, нет никакого способа получить 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
Другие вопросы по тегам