generics - instalar - haskell website
Uso de las bibliotecas estándar de haskell genéricos para los tipos de isomorfismos tipificados (2)
Hay varias bibliotecas genéricas con numerosos módulos superpuestos solo en la plataforma Haskell ( syb
, Data.Typeable
, Data.Data
, GHC.Generics
), pero estoy teniendo problemas con una tarea de programación genérica muy básica.
Quiero poder convertir entre tipos de la misma forma, es decir, quiero una función de conversión tipificada polimórfica entre tipos isomorfos, esencialmente lo que se ofrece al final de este documento (PDF) donde se mencionan las familias de tipos indexadas.
No me preocupa deshacerme de mi plantilla, sino más bien poder construir nuevas bibliotecas en torno a sumas y abstracciones de productos.
La siguiente pregunta es en términos de GHC.Generic
que pensé que era el más cercano a lo que necesitaba, pero otras soluciones son bienvenidas.
Los siguientes dos tipos tienen la misma forma.
data Pair = Pair Char Int deriving (Generic, Show)
data Pair2 = Pair2 Char Int deriving (Generic, Show)
Quiero convertir valores entre ellos usando GHC.Generics. Lo siguiente no puede realizar la comprobación de tipos debido a todos los parámetros fantasmas y otras tonterías:
f :: Pair -> Pair2
f = to . from
En última instancia, quiero una función similar a fromInteger
que tenga un valor de retorno polimórfico para cualquier instancia Generic
(o cualquier otra clase que pueda admitir esto). Supongo que estoy buscando algo como 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
Con lo anterior podemos hacer todo lo que quiero:
*Main> (coerce $A ''a'' 1) :: B
B ''a'' 1
*Main> (coerce $A ''a'' 1) :: A
A ''a'' 1
EDITAR: Así es como la función f
Nathan Howell parece funcionar a continuación, en realidad.
Preguntas
¿Es posible hacer esto con las bibliotecas actualmente en la plataforma de haskell?
Si no, ¿podría definirse una biblioteca que aprovechara el mecanismo de
deriving
existente paraGeneric
,Data
, etc. sin tener que recurrir a TH?
Es posible, y relativamente indoloro. A diferencia de usar unsafeCoerce
directamente, obtendrás un salto de compilación si los tipos no se alinean. Probablemente pueda confiar en las restricciones de igualdad en f
para proporcionar suficiente seguridad de tipo de tiempo de compilación para usar unsafeCoerce
y evitar trabajar con la familia 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
Al ejecutarlo se obtiene el resultado esperado:
*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
Si "de la misma forma" significa que los tipos de datos son iguales a los nombres de los constructores, los selectores de registros y los sinónimos de tipo, la conversión del tipo de datos es tan simple como la representación de desplazamiento.
{-# 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
Caso de prueba:
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)}
Actualizar
Algunas instancias más permiten conversiones más interesantes:
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))