2010-11-23 42 views
51

J'utilise beaucoup d'enregistrements différents dans un programme, certains d'entre eux utilisant les mêmes noms de champs, par ex.Éviter la pollution des espaces de noms dans Haskell

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

Maintenant que la fonction accesseur « foo » est définie, je reçois deux fois l'erreur « Plusieurs déclarations de ». Une façon d'éviter cela serait d'utiliser différents modules importés complets, ou simplement de renommer les champs (ce que je ne veux pas faire).

Quelle est la manière officiellement suggérée de gérer cela avec Haskell?

+3

Je partage votre douleur. Je viens du monde OO. – gawi

+2

Donc, il semble que je vais aller avec les importations qualifiées - au moins pour ce projet. Merci à tous pour vos réponses! C'est un de ces moments où les macros Scheme me manquent pour me débarrasser des violations DRY lors de l'utilisation des typeclasses ... – lbruder

+1

J'ai trouvé [cette page du projet] (https://ghc.haskell.org/trac/ghc/wiki/ Records/OverloadedRecordFields) à propos de l'extension 'OverloadedRecordFields' pour GHC afin de permettre à plusieurs types de données d'enregistrements de partager les mêmes noms de champs. – Alexey

Répondre

22

Ceci est un problème très poilu. Il y a plusieurs propositions pour fixant le système d'enregistrement. Sur une note connexe, voir TDNR et related discussion on cafe. En utilisant les fonctionnalités de langue actuellement disponibles, je pense que la meilleure option est de définir les deux types dans deux modules différents, et de faire une importation qualifiée. En plus de cela, si vous le souhaitez, vous pouvez implémenter des machines de type type.

Dans Customer.hs

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

Dans Product.hs

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

En les utilisant, dans Third.hs

module Third where 

import qualified Customer as C 
import qualified Product as P 

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

Et pourtant, je pense qu'il ne sera pas trop tard avant d'avoir rencontré le problème à propos de recursively dependent modules.

+0

Bon point sur les dépendances récursives. Mais comme indiqué dans le fil de discussion, "j'ai trouvé que l'absence de cette fonctionnalité m'obligeait à concevoir des modules dans une structure arborescente, ce qui m'a permis d'éviter de mauvaises conceptions [...] J'ai parfois déclaré des dépendances mutuelles par erreur - les messages d'erreur ont aidé à déboguer ceci. " Donc, pour l'instant, je m'en tiendrai aux importations entièrement qualifiées et diviserai mes définitions d'enregistrements en plusieurs fichiers. Peut-être aller plus tard dans l'approche typeclass, mais pour l'instant ça ressemble à un surclassement ... – lbruder

12

(Pour votre information, cette question est presque certainement un double)

Solutions:

1) Prefix les champs avec une étiquette indiquant le type (très commun)

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

2) Utilisation Les classes de type (moins courantes, les personnes se plaignent des préfixes comme cFoo sont peu pratiques mais évidemment pas si mauvaises qu'elles écriront une classe et une instance ou utiliseront TH pour faire la même chose).

class getFoo a where 
    foo :: a -> Int 

instance getFoo Customer where 
    foo = cFoo 

3) Utiliser de meilleurs noms de champs Si les champs sont en fait différents (ce qui est pas toujours vrai, mon ordinateur a un âge comme mon employé), alors c'est la meilleure solution.

+0

La syntaxe de balise plus commune que j'utilise est c_foo. – sclv

+0

L'approche typeclass semble prometteuse, mais ajoute beaucoup de duplication de code, car il y a beaucoup de champs communs dans ma définition de données (par exemple, productId, customerId etc.) que je devrais manipuler à nouveau dans chaque instance de typeclass. Au lieu de renommer, j'utiliserai plutôt les imports qualifiés pour avoir de vrais espaces de noms. – lbruder

+0

L'approche de classe de type est vraiment irréaliste. Il faudrait créer une classe de types distincte pour chaque champ nommé pour lequel on aimerait avoir des modèles de propriétés communs (getters, setters, modificateurs). C'est exactement ce que la syntaxe d'enregistrement est destinée à éviter. – ely

7

Voir aussi le paquet A: http://chrisdone.com/posts/duck-typing-in-haskell

Et si vous avez vraiment besoin dossiers extensibles maintenant, vous pouvez toujours utiliser HList. Mais je ne le recommanderais pas tant que vous ne seriez pas familier et à l'aise avec Haskell moyen-avancé, et même alors, je vérifierais si vous en aviez besoin.

haskelldb a une version légèrement plus légère: http://hackage.haskell.org/packages/archive/haskelldb/2.1.0/doc/html/Database-HaskellDB-HDBRec.html

Et puis il y a une autre version des dossiers extensible dans le cadre de la bibliothèque frp de pamplemousse: http://hackage.haskell.org/package/grapefruit-records

Encore une fois, pour vos besoins, je mords la balle et renommez simplement les champs. Mais ces références doivent montrer que lorsque vous avez vraiment besoin de toute la puissance des documents extensibles, il existe des moyens de le faire, même si aucun n'est aussi agréable qu'une extension de langage bien conçue.

+0

Wow! Je me suis rendu compte combien il reste encore à apprendre pour moi quand il s'agit de Haskell. Pour mon projet actuel, cela ressemble à une surcharge, mais je vais regarder de plus près vos liens. – lbruder

2

Il existe une extension de langage DuplicateRecordFields qui permet la duplication des fonctions de champ et permet de déduire son type par annotation de type.

Voici un petit exemple (script haskell-stack):

#!/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