2010-11-23 10 views
51

Estoy usando muchos registros diferentes en un programa, algunos de ellos usan los mismos nombres de campo, p.Evitar la contaminación del espacio de nombres en Haskell

data Customer = Customer { ..., foo :: Int, ... } 
data Product = Product { ..., foo :: Int, ... } 

Ahora bien, como la función de acceso "foo" se define dos veces, me sale el error "Múltiples declaraciones". Una forma de evitar esto sería usar diferentes módulos que se importen completamente calificados, o simplemente cambiar el nombre de los campos (que no quiero hacer).

¿Cuál es la forma oficialmente sugerida de tratar esto en Haskell?

+3

Comparto su dolor. Vengo del mundo OO. – gawi

+2

Parece que iré con las importaciones calificadas, al menos para este proyecto. ¡Gracias a todos por sus respuestas o comentarios! Este es uno de esos momentos en los que echo de menos las macros de Scheme para deshacerme de las violaciones de DRY cuando uso tipos de clases ... – lbruder

+1

He encontrado [esta página de proyecto] (https://ghc.haskell.org/trac/ghc/wiki/ Records/OverloadedRecordFields) sobre la extensión 'OverloadedRecordFields' para GHC para permitir que múltiples tipos de datos de registro compartan los mismos nombres de campo. – Alexey

Respuesta

22

Este es un problema muy peludo. Hay varias propuestas para que arreglan el sistema de registro. En una nota relacionada, vea TDNR y related discussion on cafe.

Utilizando las funciones de idioma disponibles actualmente, creo que la mejor opción es definir los dos tipos en dos módulos diferentes y realizar una importación calificada. Además de esto, si lo desea, puede implementar algún tipo de maquinaria de clase.

En Customer.hs

module Customer where 
data Customer = Customer { ..., foo :: Int, ... } 

En Product.hs

module Product where 
data Product = Product { ..., foo :: Int, ... } 

Mediante su uso, en Third.hs

module Third where 

import qualified Customer as C 
import qualified Product as P 

.. C.foo .. 
.. P.foo .. 

Sin embargo, me imagino que no será demasiado tarde antes de abordar el problema sobre recursively dependent modules.

+0

Buen punto sobre las dependencias recursivas. Pero como se indica en el hilo enlazado, "encontré que la falta de esta característica me obligaba a diseñar módulos en una estructura tipo árbol. Y esto en realidad me ayudó a evitar algunos malos diseños. A veces [...] declaraba dependencias mutuas. por error: los mensajes de error ayudaron a depurar esto ". Así que, por ahora, me quedaré con importaciones totalmente calificadas y dividiré mis definiciones de registros en múltiples archivos. Tal vez sigas el enfoque de tipo de letra más tarde, pero por ahora parece demasiado ... – lbruder

12

(FYI, esta pregunta es casi seguro que un duplicado)

Soluciones:

1) Prefijo los campos marcados con una etiqueta que indica el tipo (muy común)

data Customer = Customer {..., cFoo :: Int, ...} 

2) Uso las clases de tipo (menos comunes, las personas se quejan de prefijos como cFoo son inconvenientes, pero evidentemente no tan malos que escribirán una clase y una instancia o usarán TH para hacer lo mismo).

class getFoo a where 
    foo :: a -> Int 

instance getFoo Customer where 
    foo = cFoo 

3) un mejor uso de nombres de campo Si los campos son en realidad diferente (que no siempre es cierto, mi equipo tiene una edad igual que mi empleado), entonces esta es la mejor solución.

+0

La sintaxis de etiqueta más común, que yo uso, es c_foo. – sclv

+0

El enfoque de clase de tipo parece prometedor, pero agrega mucha duplicación de código, ya que hay muchos campos comunes en mi definición de datos (por ejemplo, idProducto, ID de cliente, etc.) que necesitaría manejar en cada instancia de clase de letra nuevamente. En lugar de cambiar el nombre, prefiero usar las importaciones calificadas para tener espacios de nombres "reales". – lbruder

+0

El enfoque de la clase de tipo es realmente poco realista. Exigiría crear una clase de tipos separada para cada campo con nombre para el cual le gustaría tener patrones de propiedades comunes (getters, setters, modificadores). Esto es exactamente lo que la sintaxis del registro debe evitar. – ely

7

Véase también el paquete tiene: http://chrisdone.com/posts/duck-typing-in-haskell

y si realmente necesidad registros extensibles ahora, siempre se puede utilizar Hlist. Pero no recomendaría esto hasta que estés realmente familiarizado y cómodo con Haskell medianamente avanzado, y aun así comprobaría tres veces que lo necesitas.

Haskelldb tiene una versión ligeramente más liviano: http://hackage.haskell.org/packages/archive/haskelldb/2.1.0/doc/html/Database-HaskellDB-HDBRec.html

Y luego hay otra versión de los registros extensibles como parte de la biblioteca de pomelo FRP: http://hackage.haskell.org/package/grapefruit-records

Una vez más, para sus fines, estaría de tripas y simplemente cambie el nombre de los campos. Pero estas referencias son para mostrar que cuando realmente se necesita todo el poder de los registros extensibles, existen formas de hacerlo, incluso si ninguno es tan agradable como lo sería una extensión de lenguaje bien diseñada.

+0

¡Guau! Me di cuenta de lo mucho que aún queda por aprender cuando se trata de Haskell. Para mi proyecto actual esto parece excesivo, pero voy a ver más de cerca tus enlaces. – lbruder

2

Hay una extensión de idioma DuplicateRecordFields que permite la duplicación de funciones de campo y hace que su tipo sea inferido por tipo de anotación.

Aquí hay un pequeño ejemplo (haskell-stack script):

#!/usr/bin/env stack 
-- stack runghc --resolver lts-8.20 --install-ghc 

{-# LANGUAGE DuplicateRecordFields #-} 

newtype Foo = Foo { baz :: String } 
newtype Bar = Bar { baz :: String } 

foo = Foo { baz = "foo text" } 
bar = Bar { baz = "bar text" } 

main = do 
    putStrLn $ "Foo: " ++ baz (foo :: Foo) -- Foo: foo text 
    putStrLn $ "Bar: " ++ baz (bar :: Bar) -- Bar: bar text 
Cuestiones relacionadas