Использование стандартных библиотек генериков haskell для типизированных изоморфизмов типов - программирование

Использование стандартных библиотек генериков haskell для типизированных изоморфизмов типов

Существует несколько универсальных библиотек с многочисленными перекрывающимися модулями только на платформе Haskell (syb, Data.Typeable, Data.Data, GHC.Generics), но у меня возникают проблемы с очень простой общей задачей программирования.

Я хочу иметь возможность конвертировать между типами одной и той же формы, т.е. я хочу использовать полиморфную, типизированную функцию преобразования между изоморфными типами, по существу, что предлагается в конце этот документ (PDF), где упоминаются семейства с индексированным типом.

Я не заинтересован в том, чтобы сломать мой шаблон, а скорее, чтобы создавать новые библиотеки вокруг сумм и абстракций продукта.

Вопрос ниже представлен в терминах GHC.Generic, который, как я думал, наиболее близок к тому, что мне нужно, но приветствуем другие решения.


Следующие два типа имеют одинаковую форму

data Pair = Pair Char Int deriving (Generic, Show)
data Pair2 = Pair2 Char Int deriving (Generic, Show)

Я хочу преобразовать значения между ними с помощью GHC.Generics. Ниже приведено описание typecheck из-за всех параметров phantom и других нонсенсов:

f :: Pair -> Pair2
f = to . from

В конечном итоге я хочу использовать функцию fromInteger, которая имеет полиморфное возвращаемое значение для любого экземпляра Generic (или любого другого класса, который мог бы поддерживать этот). Наверное, я ищу что-то вроде GHC.Generics:

--class:
type family NormalForm a
class ToGeneric a where
    to :: a -> NormalForm a
class FromGeneric b where
    from :: NormalForm b -> b

--examples:
data A = A Char Int deriving Show
data B = B Char Int deriving Show

type instance NormalForm A = (Char,Int)
instance ToGeneric A where
    to (A a b) = (a,b)
instance FromGeneric A where
    from (a,b) = A a b

type instance NormalForm B = (Char,Int)
instance ToGeneric B where
    to (B a b) = (a,b)
instance FromGeneric B where
    from (a,b) = B a b

-- the function I'm looking for
coerce :: (ToGeneric a, FromGeneric b, NormalForm a ~ NormalForm b)=> a -> b
coerce = from . to

С помощью вышеизложенного мы можем делать все, что я хочу:

*Main> (coerce $A 'a' 1) :: B
B 'a' 1
*Main> (coerce $A 'a' 1) :: A
A 'a' 1

EDIT: Вот как работает функция Натана Хауэлла f.

Вопросы

  • Возможно ли это сделать с библиотеками, находящимися в настоящее время на платформе haskell?

  • Если нет, можно ли определить библиотеку, которая использовала существующий механизм deriving для Generic, Data и т.д., не прибегая к TH?

4b9b3361

Ответ 1

Если "одинаковой формы" означает, что типы данных равны именам конструкторов, селекторам записей и синонимам типов, тогда преобразование данных типа так же просто, как и представление перемещения.

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts, FlexibleInstances #-}

import GHC.Generics

conv
  :: (Generic a, Generic b, Conv (Rep a) (Rep b))
  => a -> b
conv = to . cv . from

class Conv a b where
  cv :: a x -> b x

-- skip irrelevant parts: datatype name, constructor name, selector
instance Conv f1 f2 => Conv (M1 i1 c1 f1) (M1 i2 c2 f2) where
  cv = M1 . cv . unM1

instance (Conv a1 a2, Conv b1 b2) => Conv (a1 :*: b1) (a2 :*: b2) where
  cv ~(a :*: b) = cv a :*: cv b

instance (Conv a1 a2, Conv b1 b2) => Conv (a1 :+: b1) (a2 :+: b2) where
  cv (L1 a) = L1 $ cv a
  cv (R1 b) = R1 $ cv b

-- copy values
instance Conv U1 U1 where cv = id
instance Conv (K1 R c) (K1 R c) where cv = id

Тестовый пример:

data A = A1 String Int | A2 (Int,Int) deriving (Generic, Show)
data B = B1 [Char] Int | B2 { xy :: (Int,Int) } deriving (Generic, Show)
data X = X Int Int deriving (Generic, Show)

*Main> conv $ X 3 14 :: (Int,Int)
(3,14)
*Main> conv $ A1 "hello" 42 :: B
B1 "hello" 42
*Main> conv $ A2 (13,42) :: B
B2 {xy = (13,42)}

Update

Несколько примеров позволяют более интересные преобразования:

instance Conv U1 (M1 S s (K1 R ())) where
  cv _ = M1 $ K1 ()
-- *> conv (Nothing :: Maybe Int) :: Either () Int
-- Left ()

instance Conv (M1 S s (K1 R ())) U1 where
  cv _ = U1
-- *> conv (Left () :: Either () Int) :: Maybe Int
-- Nothing

-- this one requires OverlappingInstances
instance (Generic c1, Generic c2, Conv (Rep c1) (Rep c2))
  => Conv (K1 R c1) (K1 R c2)
  where
    cv (K1 x) = K1 $ conv x
 -- *> conv (Right Nothing :: Either () (Maybe Int)) :: Maybe (Either () Int)
 -- Just (Left ())

 -- data List a = Empty | Cons a (List a) deriving (Generic, Show)
 -- *> conv [1,2,3::Int] :: List Int
 -- Cons 1 (Cons 2 (Cons 3 Empty))

Ответ 2

Это возможно и относительно безболезненно. В отличие от использования unsafeCoerce напрямую, вы получите разрыв сборки, если типы не совпадают. Вероятно, вы можете полагаться на ограничения равенства на f, чтобы обеспечить достаточную безопасность типа времени компиляции для использования unsafeCoerce и избежать работы с семейством Rep.

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeFamilies #-}

import GHC.Generics

data Pair1 = Pair1 Char Int deriving (Generic, Show)
data Pair2 = Pair2 Char Int deriving (Generic, Show)

data Triple1 = Triple1 Char Int Double deriving (Generic, Show)
data Triple2 = Triple2 Char Int Double deriving (Generic, Show)

f :: (Generic a, Generic c, Rep a ~ D1 da (C1 ca f), Rep c ~ D1 db (C1 cb f))
  => a -> c
f = to . M1 . M1 . unM1 . unM1 . from
-- this might also be acceptable:
-- f = unsafeCoerce

p1 :: Pair1 -> Pair2
p1 = f

p2 :: Pair2 -> Pair1
p2 = f

t1 :: Triple1 -> Triple2
t1 = f

t2 :: Triple2 -> Triple1
t2 = f

Выполнение этого результата дает ожидаемый результат:

*Main> p1 $ Pair1 'x' 1
Pair2 'x' 1
*Main> p2 $ Pair2 'x' 1
Pair1 'x' 1
*Main> t1 $ Triple1 'y' 2 3.0
Triple2 'y' 2 3.0
*Main> t2 $ Triple2 'y' 2 3.0
Triple1 'y' 2 3.0