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.


person gatoatigrado    schedule 11.03.2012    source источник


Ответы (1)


Вы можете сделать это относительно чисто и эффективно, используя универсальную поддержку программирования. документация для 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)
person reinerp    schedule 12.03.2012