haskell generic-programming

¿Cómo puedo producir un tipo de etiqueta para cualquier tipo de datos para usar con DSum, sin Template Haskell?



generic-programming (2)

A diferencia de lo que algunos han afirmado, es perfectamente sensato (y de hecho bastante sencillo, con la biblioteca correcta - generics-sop ) definir dicho tipo. Esencialmente toda la maquinaria es provista por esta biblioteca ya:

{-# LANGUAGE PatternSynonyms, PolyKinds, DeriveGeneric #-} import Generics.SOP import qualified GHC.Generics as GHC import Data.Dependent.Sum data Tup2List :: * -> [*] -> * where Tup0 :: Tup2List () ''[] Tup1 :: Tup2List x ''[ x ] TupS :: Tup2List r (x '': xs) -> Tup2List (a, r) (a '': x '': xs) newtype GTag t i = GTag { unTag :: NS (Tup2List i) (Code t) }

El tipo GTag es lo que llamas Magic . La ''magia'' real ocurre en la familia de tipos de Code , que calcula la representación genérica de un tipo, como una lista de listas de tipos. El tipo NS (Tup2List i) xs significa que, precisamente para uno de xs , Tup2List i tiene, esto es simplemente una prueba de que una lista de argumentos es isomorfa a alguna tupla.

Todas las clases que necesita pueden derivarse:

data SomeUserType = Foo Int | Bar Char | Baz Bool String deriving (GHC.Generic, Show) instance Generic SomeUserType

Puede definir algunos sinónimos de patrones para las etiquetas válidas para este tipo:

pattern TagFoo :: () => (x ~ Int) => GTag SomeUserType x pattern TagFoo = GTag (Z Tup1) pattern TagBar :: () => (x ~ Char) => GTag SomeUserType x pattern TagBar = GTag (S (Z Tup1)) pattern TagBaz :: () => (x ~ (Bool, String)) => GTag SomeUserType x pattern TagBaz = GTag (S (S (Z (TupS Tup1))))

y una prueba simple:

fun0 :: GTag SomeUserType i -> i -> String fun0 TagFoo i = replicate i ''a'' fun0 TagBar c = c : [] fun0 TagBaz (b,s) = (if b then show else id) s fun0'' = /(t :& v) -> fun0 t v main = mapM_ (putStrLn . fun0'' . toTagVal) [ Foo 10, Bar ''q'', Baz True "hello", Baz False "world" ]

Como esto se expresa en términos de una función de tipo genérico, puede escribir operaciones genéricas sobre etiquetas. Por ejemplo, exists x . (GTag tx, x) exists x . (GTag tx, x) es isomorfo a t para cualquier Generic t :

type GTagVal t = DSum (GTag t) I pattern (:&) :: forall (t :: * -> *). () => forall a. t a -> a -> DSum t I pattern t :& a = t :=> I a toTagValG_Con :: NP I xs -> (forall i . Tup2List i xs -> i -> r) -> r toTagValG_Con Nil k = k Tup0 () toTagValG_Con (I x :* Nil) k = k Tup1 x toTagValG_Con (I x :* y :* ys) k = toTagValG_Con (y :* ys) (/tp vl -> k (TupS tp) (x, vl)) toTagValG :: NS (NP I) xss -> (forall i . NS (Tup2List i) xss -> i -> r) -> r toTagValG (Z x) k = toTagValG_Con x (k . Z) toTagValG (S q) k = toTagValG q (k . S) fromTagValG_Con :: i -> Tup2List i xs -> NP I xs fromTagValG_Con i Tup0 = case i of { () -> Nil } fromTagValG_Con x Tup1 = I x :* Nil fromTagValG_Con xs (TupS tg) = I (fst xs) :* fromTagValG_Con (snd xs) tg toTagVal :: Generic a => a -> GTagVal a toTagVal a = toTagValG (unSOP $ from a) ((:&) . GTag) fromTagVal :: Generic a => GTagVal a -> a fromTagVal (GTag tg :& vl) = to $ SOP $ hmap (fromTagValG_Con vl) tg

En cuanto a la necesidad de Tup2List , es necesario por el simple motivo de que representa un constructor de dos argumentos ( Baz Bool String ) como una etiqueta sobre una tupla de (Bool, String) en su ejemplo.

También podría implementarlo como

type HList = NP I -- from generics-sop data Tup2List i xs where Tup2List :: Tup2List (HList xs) xs

que representa los argumentos como una lista heterogénea, o incluso más simple

newtype GTag t i = GTag { unTag :: NS ((:~:) i) (Code t) } type GTagVal t = DSum (GTag t) HList fun0 :: GTag SomeUserType i -> HList i -> String fun0 TagFoo (I i :* Nil) = replicate i ''a'' fun0 ...

Sin embargo, la representación de la tupla tiene la ventaja de que las tuplas únicas se ''proyectan'' al único valor que está en la tupla (es decir, en lugar de (x, ()) ). Si representa argumentaciones de la manera obvia, funciones como fun0 deben coincidir con el patrón para recuperar el valor único almacenado en un constructor.

fondo

Quiero escribir un código de biblioteca, que internamente utiliza DSum para manipular el tipo de datos de un usuario. DSum requiere un tipo de ''etiqueta'' que tiene un argumento de tipo único. Sin embargo, quiero que mi código funcione con cualquier tipo de concreto antiguo. Entonces, me gustaría simplemente tomar el tipo de usuario y producir automáticamente el tipo de etiqueta. He hecho una pregunta muy similar aquí. ¿Cómo puedo producir programáticamente este tipo de datos desde el otro? y obtuve una gran respuesta. Esa respuesta se basa en TH, principalmente para que pueda crear declaraciones de alto nivel. Sin embargo, en realidad no me importa la declaración de nivel superior, y preferiría evitar el TH si es posible.

pregunta

[Cómo] puedo escribir, con alguna técnica de programación genérica, un tipo de datos

data Magic t a ...

donde se da algún tipo de suma arbitraria, por ejemplo

data SomeUserType = Foo Int | Bar Char | Baz Bool String

Magic SomeUserType es equivalente a este tipo de ''etiqueta'' que se puede usar con DSum?

data TagSomeUserType a where TagFoo :: TagSomeUserType Int TagBar :: TagSomeUserType Char TagBaz :: TagSomeUserType (Bool, String)


No estoy seguro de que pueda prescindir de TH ya que, como se señala en los comentarios, aún necesita hacer un tipo al final del día. Como señala Benjamin, probablemente esté buscando una data family .

Lo que llamas Magic , me referiré a Tagged .

Aquí está el código ajustado que necesitará para tag.hs

{-# LANGUAGE TemplateHaskell #-} module Tag where import Language.Haskell.TH makeTag :: Name -> Name -> DecsQ makeTag name tag = do -- Reify the data declaration to get the constructors. -- Note we are forcing there to be no type variables... (TyConI (DataD _ _ [] _ cons _)) <- reify name pure [ DataInstD [] tag [(ConT name), (VarT (mkName "a"))] Nothing (tagCon <$> cons) [] ] where -- Given a constructor, construct the corresponding constructor for -- Tag GADT tagCon :: Con -> Con tagCon (NormalC conName args) = let tys = fmap snd args tagType = foldl AppT (TupleT (length tys)) tys in GadtC [mkName ("Tag" ++ nameBase conName)] [] (AppT (AppT (ConT tag) (ConT name)) tagType)

Y, un caso de uso de muestra (hasta llegar a algo relacionado con DSum ):

{-# LANGUAGE TemplateHaskell, GADTs, TypeFamilies #-} module Test where import Data.Dependent.Sum import Data.Functor.Identity import Tag -- Some data types data SomeUserType1 = Foo Int | Bar String data SomeUserType2 = Fooo Int | Baar Char | Baaz Bool String data SomeAwkUserType = Foooo Int -- Data family for all Tagged things data family Tagged t a -- Generated data family instances makeTag ''''SomeUserType1 ''''Tagged makeTag ''''SomeUserType2 ''''Tagged makeTag ''''SomeAwkUserType ''''Tagged -- A sample DSum''s use case toString :: DSum (Tagged SomeUserType1) Identity -> String toString (TagFoo :=> Identity int) = show int toString (TagBar :=> Identity str) = str

Esto termina generando instancias de etiqueta de data family para cada tipo. Hazme saber si tienes alguna pregunta.