2009-12-13 6 views
11

Supposons que j'ai deux types de données Foo et Bar. Foo a des champs x et y. La barre a les champs x et z. Je veux être en mesure d'écrire une fonction qui prend un Foo ou un Bar comme paramètre, extrait la valeur x, effectue un calcul dessus, puis retourne un nouveau Foo ou Bar avec la valeur x définie en conséquence.Syntaxe d'enregistrement Haskell et classes de type

Voici une approche:

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 

Le problème est que tous ces accesseurs sont douloureuses à écrire, surtout si je remplace Foo et Bar avec des types de données réelles qui ont beaucoup de domaines.

La syntaxe d'enregistrement de Haskell donne une manière beaucoup plus agréable de définir ces enregistrements. Mais, si je tente de définir les dossiers comme celui-ci

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

Je reçois une erreur disant que x est défini plusieurs fois. Et, je ne vois aucun moyen de faire partie d'une classe de type afin que je puisse les passer à modifyX.

Existe-t-il une bonne manière propre de résoudre ce problème, ou est-ce que je suis bloqué avec définir mes propres getters et setters? En d'autres termes, existe-t-il un moyen de connecter les fonctions créées par la syntaxe d'enregistrement avec les classes de types (les getters et les setters)?

EDIT

est ici le vrai problème, je suis en train de résoudre. J'écris une série de programmes liés qui utilisent tous System.Console.GetOpt pour analyser leurs options de ligne de commande. Il y aura beaucoup d'options de ligne de commande communes à tous ces programmes, mais certains programmes peuvent avoir des options supplémentaires. Je voudrais que chaque programme soit capable de définir un enregistrement contenant toutes ses valeurs d'option. Je commence alors avec une valeur d'enregistrement par défaut qui est ensuite transformée par une monade StateT et GetOpt pour obtenir un enregistrement final reflétant les arguments de ligne de commande. Pour un seul programme, cette approche fonctionne très bien, mais j'essaie de trouver un moyen de réutiliser le code dans tous les programmes.

+3

Si vous aviez un seul type de données 'data FooBar = Foo {x :: Int, y :: Int} | Bar {x :: Int, z :: Int} 'vous n'auriez pas ce problème. Si vos types de données se trouvaient dans des modules différents, vous pourriez utiliser '{- # LANGUAGE DisambiguateRecordFields # -}'. Des raisons de coller avec votre design actuel? – ephemient

Répondre

5

Vous voulez extensible records qui, je crois, est l'un des sujets les plus abordés dans Haskell. Il semble qu'il n'y ait pas actuellement beaucoup de consensus sur la façon de l'appliquer.

Dans votre cas, il semble que, au lieu d'un enregistrement ordinaire, vous pourriez utiliser une liste hétérogène comme celles implémentées dans HList.

Là encore, il semble que vous n'ayez ici que deux niveaux: commun et programme. Alors peut-être vous devriez simplement définir un type d'enregistrement commun pour les options communes et un type d'enregistrement spécifique au programme pour chaque programme, et utiliser StateT sur un tuple de ces types. Pour les choses courantes, vous pouvez ajouter des alias qui composent fst avec les accesseurs communs afin qu'il soit invisible pour les appelants.

+0

L'idée intéressante d'avoir un type d'enregistrement commun et un type spécifique au programme. Je pourrais avoir plus de groupements de communalité en plus du commun et du programme (pas encore sûr), mais je pourrais facilement étendre votre approche pour soutenir ceci. (Par exemple, je pourrais passer en tant que liste associative d'enregistrements à travers le StateT, et chaque option devrait savoir comment rechercher son enregistrement correspondant dans la liste par son nom.) –

3

Vous pouvez utiliser un code tel que

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-ce que vous modélisez dans votre code? Si nous en savions plus sur le problème, nous pourrions suggérer quelque chose de moins gênant que ce design axé sur l'objet, avec un langage fonctionnel.

+0

Merci pour la réponse. Votre approche simplifie quelque peu les choses. J'ai modifié ma question initiale pour expliquer davantage le vrai problème que j'essaie de résoudre. Je suis d'accord que l'utilisation d'une approche OO avec un langage fonctionnel n'est généralement pas le meilleur moyen. Je suis nouveau dans les langages fonctionnels et essaye toujours de sortir de ma pensée OO. :-) –

1

Si vous créez les instances de Pliable, vous obtenez une fonction toList que vous pouvez utiliser comme base de votre accesseur.

Si Pliable ne vous concerne pas, alors peut-être que la bonne approche est de définir l'interface que vous voulez comme une classe de type et de trouver un bon moyen d'autogénérer les valeurs dérivées.

Peut-être en dérivant de faire

deriving(Data) 

vous pouvez utiliser combinateurs GMAP baser votre accès au large.

2

Cela me semble être un travail pour les génériques. Si vous pouvez marquer votre Int avec différents newtypes, vous serez alors en mesure d'écrire (avec uniplaque, le module PlateData):

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 ] 

Ceci extrait tous les AUTRUI de partout à l'intérieur du Opte.

Une modification est également possible.

Questions connexes