haskell - есть ли способ генерировать "производные" экземпляры для грубо-кортежно-изоморфных типов данных?
Предположим, у меня есть тип данных, как
data D a = D a a a
и класс типов
class C c ...
instance (C c1, C c2) => C (c1, c2)
Затем я хочу иметь возможность написать
data D a = D a a a deriving C
и это генерирует экземпляр,
instance C ((a, a), a) => C (D a)
используя изоморфизм модулярно-ленивых вычислений,
D a ~ ((a, a), a)
Примечание Используя новый тип и GeneralizedNewtypeDeriving
не будет работать, если, например, data D m = D (m Integer) (m Integer)
,
Примечание 2 Этот вопрос имеет отношение к выразительности языка Haskell в целом - в таких языках, как Python, есть так называемые именованные кортежи, которые можно использовать везде, где используются кортежи; этот вопрос показывает, где / как я не знаю, как подражать тому же самому в Haskell.
1 ответ
Вы можете сделать это относительно чисто и эффективно, используя общую поддержку программирования GHC 7.4. Документация по GHC.Generics может быть полезной. Вот пример.
Рассмотрим следующий пример класса и несколько примеров:
class C a where
-- | Double all numbers
double :: a -> a
instance C Int where
double i = 2 * i
instance (C a, C b) => C (a, b) where
double (a, b) = (double a, double b)
Нам нужны языковые прагмы и импорт:
{-# LANGUAGE TypeOperators, DefaultSignatures, DeriveGeneric, FlexibleContexts, FlexibleInstances #-}
module Example where
import GHC.Generics hiding(C, D)
Теперь приведем несколько "общих примеров". Универсальные типы имеют фантомный параметр x
, что делает головы экземпляра немного сложнее:
-- "Insert" a normal value into a generic value
instance C c => C (K1 i c x) where
double (K1 c) = K1 (double c)
-- Ignore meta-information (constructor names, type names, field names)
instance C (f x) => C (M1 i c f x) where
double (M1 f) = M1 (double f)
-- Tuple-like instance
instance (C (f x), C (g x)) => C ((f :*: g) x) where
double (f :*: g) = double f :*: double g
Теперь мы переопределим наш класс C
чтобы воспользоваться GC
class C a where
-- | Double all numbers
double :: a -> a
-- specify the default implementation for double
default double :: (Generic a, C (Rep a ())) => a -> a
double = to0 . double . from0
-- from, with a more specialised type, to avoid ambiguity
from0 :: Generic a => a -> Rep a ()
from0 = from
-- to, with a more specialised type, to avoid ambiguity
to0 :: Generic a => Rep a () -> a
to0 = to
Теперь мы можем очень легко определить некоторые экземпляры:
data D a = D a a a deriving Generic
instance C a => C (D a)
data D2 m = D2 (m Int) (m Int) deriving Generic
instance C (D2 D)