2011-04-25 25 views
16

Estoy trabajando en el diseño de una aplicación web de mayor tamaño utilizando Haskell. Esto es puramente para mi educación e interés.Modelado de datos de dominio en Haskell

Estoy empezando por escribir mi dominio/objetos de valor. Un ejemplo es un usuario. Esto es lo que se me ocurrió hasta ahora

module Model (User) where 

class Audited a where 
    creationDate :: a -> Integer 
    lastUpdatedDate :: a -> Integer 
    creationUser :: a -> User 
    lastUpdatedUser :: a -> User 

class Identified a where 
    id :: a -> Integer 

data User = User { userId :: Integer 
       , userEmail :: String 
       , userCreationDate :: Integer 
       , userLastUpdatedDate :: Integer 
       , userCreationUser :: User 
       , userLastUpdatedUser :: User 
       } 

instance Identified User where 
    id u = userId u 

instance Audited User where 
    creationDate u = userCreationDate 
    lastUpdatedDate u = userLastUpdatedDate 
    creationUser u = userCreationUser 
    lastUpdatedUser u = userLastUpdatedUser 

Mi aplicación tendrá aproximadamente 20 tipos como el tipo anterior. Cuando digo "como el tipo anterior", quiero decir que tendrán una identificación, información de auditoría y cierta información específica del tipo (como el correo electrónico en el caso del Usuario).

Lo que no puedo dejar de pensar es que cada uno de mis campos (por ejemplo, User.userEmail) crea una nueva función fieldName :: Type -> FieldType. Con 20 tipos diferentes, parece que el espacio de nombres se llenará bastante rápido. Además, no me gusta tener que llamar mi campo de ID de usuario userId. Prefiero llamarlo id. ¿Hay alguna forma de evitar esto?

Tal vez debería mencionar que vengo del mundo imperativo, por lo que este FP es bastante nuevo (pero bastante emocionante) para mí.

+0

Puede usar el sistema de módulos para controlar el alcance y la calificación del nombre. –

+0

¿Esto significaría crear un módulo para cada tipo y hacer un 'Modelo de usuario calificado como usuario' (por ejemplo)? –

+0

Oh, simplemente usando 'import qualified' para controlar el problema de explosión del campo de etiqueta de registro. –

Respuesta

13

Sí, el espacio de nombres puede ser una especie de dolor en Haskell. Por lo general, termino endureciendo mis abstracciones hasta que no hay tantos nombres. También permite más reutilización.Por la tuya, me gustaría hacer un tipo de datos en lugar de una clase para la información de auditoría:

data Audit = Audit { 
    creationDate :: Integer, 
    lastUpdatedDate :: Integer, 
    creationUser :: User, 
    lastUpdatedUser :: User 
} 

Y luego emparejar eso con los datos específicos del tipo:

data User = User { 
    userAudit :: Audit, 
    userId :: Integer, 
    userEmail :: String 
} 

Puede seguir utilizando esas clases de tipos si quieres:

class Audited a where 
    audit :: a -> Audit 

class Identified a where 
    ident :: a -> Integer 

Sin embargo, como se desarrolla su diseño, estar abiertos a la posibilidad de que esas clases de tipos disuelven en el aire. Tipo de objeto clases de tipo - aquellas donde cada método toma un solo parámetro del tipo a - tienen una forma de simplificarse.

Otra manera de abordar esto podría ser para clasificar los objetos con un tipo paramétrico:

data Object a = Object { 
    objId :: Integer, 
    objAudit :: Audit, 
    objData :: a 
} 

Compruébelo usted mismo, Object es una Functor!

instance Functor Object where 
    fmap f (Object id audit dta) = Object id audit (f dta) 

Estaría más inclinado a hacerlo de esta manera, de acuerdo con mi corazonada de diseño. Es difícil decir qué camino es mejor sin saber más acerca de sus planes. Y mira, la necesidad de esas clases de tipos se disolvió. :-)

+2

En línea con el comentario de Robin Green, absolutamente mire en los lentes (' data-accessor' o 'fclabels' en hackage) para trabajar con registros de registros (de registros ...). – luqui

0

Una opción simple es, en lugar de dar el máximo en las clases de tipos, hacen todas sus tipos de variedades de una sola algebraica tipo de datos:

data DomainObject = User { 
         objectID :: Int, 
         objectCreationDate :: Date 
         ... 
        } 
        | SomethingElse { 
         objectID :: Int, 
         objectCreationDate :: Date, 
         somethingProperty :: Foo 
         ... 
        } 
        | AnotherThing { 
         objectID :: Int, 
         objectCreationDate :: Date, 
         anotherThingProperty :: Bar 
         ... 
        } 

Es torpe, ya que requiere tener todas las estructuras de datos en una sola archivo, pero al menos le permite usar la misma función (objectID) para obtener el ID de un objeto.

+2

Como todo aquí tiene una ID y una fecha de creación, tendría sentido agrupar esas dos cosas en un tipo de datos común. Tengo la sensación de que las lentes podrían ser útiles aquí. –

+0

@Robin Green: ¿dónde puedo encontrar un ejemplo de lentes que se utilizan? Encontré esto: http://hackage.haskell.org/package/lenses, pero no hay ningún ejemplo. ¡Gracias! –

+0

No importa, encontré esto: http://hackage.haskell.org/packages/archive/lenses/0.1.4/doc/html/Data-Lenses.html –

8

Este es un problema conocido con los registros de Haskell. Ha habido algunas sugerencias (especialmente TDNR) para mitigar los efectos, pero aún no han surgido soluciones.

Si no le importa poner cada uno de los objetos de datos en un módulo separado, a continuación, puede utilizar espacios de nombres para diferenciar entre las funciones:

import qualified Model.User as U 
import qualified Model.Privileges as P 

someUserId user = U.id user 
somePrivId priv = P.id priv 

En cuanto al uso de id en lugar de userId; es posible si oculta el id que se importa desde el Preludio de forma predeterminada. Utilice el siguiente como su primera declaración de importación:

import Prelude hiding (id) 

y ahora la función habitual id no estará en su alcance. Si lo necesita por algún motivo, puede acceder a él con un nombre completo, es decir, Prelude.id.

Piénselo detenidamente antes de crear un nombre que no coincida con el Preludio. A menudo puede ser confuso para el programador, y es un poco incómodo trabajar con él. Tal vez sea mejor usar un nombre corto y genérico, como oId.

Cuestiones relacionadas