Supongamos que tengo dos tipos de datos Foo y Bar. Foo tiene los campos xey. Bar tiene los campos x y z. Quiero poder escribir una función que tome un Foo o una Barra como parámetro, extrae el valor de x, realiza algunos cálculos en él, y luego devuelve un Foo o Bar nuevo con el valor x establecido en consecuencia.Haskell sintaxis de registro y clases de tipo
Aquí es uno de los enfoques:
class HasX a where
getX :: a -> Int
setX :: a -> Int -> a
data Foo = Foo Int Int deriving Show
instance HasX Foo where
getX (Foo x _) = x
setX (Foo _ y) val = Foo val y
getY (Foo _ z) = z
setY (Foo x _) val = Foo x val
data Bar = Bar Int Int deriving Show
instance HasX Bar where
getX (Bar x _) = x
setX (Bar _ z) val = Bar val z
getZ (Bar _ z) = z
setZ (Bar x _) val = Bar x val
modifyX :: (HasX a) => a -> a
modifyX hasX = setX hasX $ getX hasX + 5
El problema es que todos estos captadores y definidores son dolorosas para escribir, especialmente si reemplazo Foo y Bar con tipos de datos del mundo real que tienen una gran cantidad de campos.
La sintaxis de registro de Haskell ofrece una manera mucho más agradable de definir estos registros. Pero, si lo intento de definir los registros como esto
data Foo = Foo {x :: Int, y :: Int} deriving Show
data Bar = Foo {x :: Int, z :: Int} deriving Show
voy a conseguir un error que dice que x se define en múltiples ocasiones. Y, no veo ninguna forma de hacer estas partes de una clase de tipo para poder pasarlos a modifyX.
¿Hay alguna manera limpia de resolver este problema, o me veo obligado a definir mis propios getters y setters? Dicho de otra manera, ¿hay alguna forma de conectar las funciones creadas por la sintaxis del registro con las clases de tipos (tanto los getters como los setters)?
EDITAR
Aquí es el verdadero problema que estoy tratando de resolver. Estoy escribiendo una serie de programas relacionados que usan System.Console.GetOpt para analizar sus opciones de línea de comandos. Habrá una gran cantidad de opciones de línea de comandos que son comunes en todos estos programas, pero algunos de los programas pueden tener opciones adicionales. Me gustaría que cada programa pueda definir un registro que contenga todos sus valores de opción. Luego empiezo con un valor de registro predeterminado que luego se transforma a través de una mónada StateT y GetOpt para obtener un registro final que refleje los argumentos de la línea de comandos. Para un solo programa, este enfoque funciona realmente bien, pero estoy tratando de encontrar una forma de reutilizar el código en todos los programas.
Si tenía un solo tipo de datos 'datos FooBar = Foo {x :: Int, y :: Int} | Bar {x :: Int, z :: Int} 'no tendrías este problema. Si sus tipos de datos estaban en módulos diferentes, podría usar '{- # LANGUAGE DisambiguateRecordFields # -}'. ¿Alguna razón para seguir con tu diseño actual? – ephemient