Pregunta

Supongamos que tengo dos tipos de datos Foo y Bar.Foo tiene campos xey.La barra tiene campos x y z.Quiero poder escribir una función que tome Foo o Bar como parámetro, extraiga el valor de x, realice algunos cálculos y luego devuelva un nuevo Foo o Bar con el valor de x establecido en consecuencia.

Aquí hay un enfoque:

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 es difícil escribir todos esos captadores y definidores, especialmente si reemplazo Foo y Bar con tipos de datos del mundo real que tienen muchos campos.

La sintaxis de registros de Haskell ofrece una forma mucho más sencilla de definir estos registros.Pero, si trato de definir los registros de esta manera

data Foo = Foo {x :: Int, y :: Int} deriving Show
data Bar = Foo {x :: Int, z :: Int} deriving Show

Recibiré un error que indica que x está definido varias veces.Y no veo ninguna forma de hacer que estos formen parte de una clase de tipo para poder pasarlos a modificarX.

¿Existe una forma agradable y limpia de resolver este problema o tengo que definir mis propios captadores y definidores?Dicho de otra manera, ¿hay alguna manera de conectar las funciones creadas por la sintaxis de registro con clases de tipos (tanto los captadores como los definidores)?

EDITAR

Este 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á muchas opciones de línea de comandos que son comunes en estos programas, pero algunos de ellos pueden tener opciones adicionales.Me gustaría que cada programa pudiera definir un registro que contenga todos los valores de sus opciones.Luego comienzo con un valor de registro predeterminado que luego se transforma mediante una mónada StateT y GetOpt para obtener un registro final que refleja los argumentos de la línea de comandos.Para un solo programa, este enfoque funciona muy bien, pero estoy tratando de encontrar una manera de reutilizar el código en todos los programas.

¿Fue útil?

Solución

Quieres registros extensibles que, según tengo entendido, es uno de los temas más comentados en Haskell.Parece que actualmente no hay mucho consenso sobre cómo implementarlo.

En su caso, parece que tal vez en lugar de un registro ordinario podría usar una lista heterogénea como las implementadas en Lista H.

Por otra parte, parece que aquí solo tienes dos niveles:común y programa.Entonces, tal vez debería definir un tipo de registro común para las opciones comunes y un tipo de registro específico para cada programa, y ​​usar StateT en una tupla de esos tipos.Para las cosas comunes, puedes agregar alias que compongan fst con los descriptores de acceso comunes para que sea invisible para quienes llaman.

Otros consejos

Se puede usar un código como

data Foo = Foo { fooX :: Int, fooY :: Int } deriving (Show)
data Bar = Bar { barX :: Int, barZ :: Int } deriving (Show)

instance HasX Foo where
  getX = fooX
  setX r x' = r { fooX = x' }

instance HasX Bar where
  getX = barX
  setX r x' = r { barX = x' }

¿Qué estás modelando en su código? Si supiéramos más sobre el problema, podríamos sugerir algo menos incómodo que este diseño orientado a objetos metió con calzador en un lenguaje funcional.

Me parece que un trabajo para los genéricos. Si se pudiera etiquetar su Int con diferentes Newtypes, entonces sería capaz de escribir (con Uniplate, PlateData módulo):

data Foo = Foo Something Another deriving (Data,Typeable)
data Bar = Bar Another Thing deriving (Data, Typerable)

data Opts = F Foo | B Bar

newtype Something = S Int
newtype Another = A Int
newtype Thing = T Int

getAnothers opts = [ x | A x <- universeBi opts ]

Esto sería extraer todos de otro desde cualquier lugar dentro de los TPO.

modificación es posible también.

Si realiza los tipos de casos plegable se obtiene una función toList que se puede utilizar como base de su descriptor de acceso.

Si no lo hace por plegable que nada, entonces tal vez el enfoque correcto es definir la interfaz que desea como una clase tipo y encontrar una buena manera de autogenerar los valores derivados.

Tal vez mediante la derivación de hacer

deriving(Data)

podría utilizar combinadores gmap basar su acceso fuera.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top