Des valeurs comprises dans les types de structures similaires dans Haskell
-
22-09-2019 - |
Question
Excusez-moi pour mon Haskell-fu extrêmement limité.
J'ai une série de types de data
, définis dans différents modules, qui sont structurés de la même façon:
-- in module Foo
data Foo = Foo [Param]
-- in module Bar
data Bar = Bar [Param]
-- * many more elsewhere
Je voudrais avoir un ensemble de fonctions qui fonctionnent sur la liste des params, par exemple pour ajouter et supprimer des éléments de la liste (retourner une nouvelle Foo
ou Bar
avec une autre liste de params, selon le cas).
Pour autant que je peux dire, même si je crée une classe de types et créer des instances pour chaque type, je dois définir toutes ces fonctions à chaque fois, à savoir:
-- in some imported module
class Parameterized a where
addParam :: a -> Param -> a
-- ... other functions
-- in module Foo
instance Parameterization Foo where
addParam (Foo params) param = Foo (param:params)
-- ... other functions
-- in module Bar
instance Parameterization Bar where
-- this looks familiar...
addParam (Bar params) param = Bar (param:params)
-- ... other functions
Cela se sent fastidieux - bien au-delà du degré où je commence à penser que je fais quelque chose de mal. Si vous ne pouvez pas correspondance de motif quel que soit le constructeur (?) Pour extraire une valeur, comment peut-partout comme ceci réduit?
Pour réfuter une ligne possible d'argument: oui, je sais que je pourrais simplement avoir un ensemble de fonctions (addParam
, etc.), qui dresserait une liste explicitement chaque match constructeur et le modèle mis les params - mais comme je construis cette assez modulaire (modules Foo
et Bar
sont assez autonomes), et je suis le prototypage d'un système où il y aura des dizaines de ces types, une liste verbeux centralisée des constructeurs de type semble ... mal.
Il est tout à fait possible (probable?) Que mon approche est tout simplement erronée et que c'est nulle part près de la bonne façon de structurer de toute façon la hiérarchie de type - mais comme je ne peux pas avoir un seul type de data
quelque part et ajouter un nouveau constructeur pour le type dans chacun de ces modules (?) Je suis perplexe sur la façon d'obtenir un joli « plug-in » se sentir sans avoir à redéfinir les fonctions utilitaires simples à chaque fois. Toutes les suggestions et toutes sortes ont accepté avec joie.
La solution
Vous pouvez avoir des implémentations par défaut des fonctions dans une classe de types, par exemple
class Parameterized a where
params :: a -> [Param]
fromParams :: [Param] -> a
addParam :: a -> Param -> a
addParam x par = fromParams $ par : params x
-- ... other functions
instance Parameterized Foo where
params (Foo pars) = pars
fromParams = Foo
Cependant, votre conception ne semble suspect.
Autres conseils
Vous pouvez utiliser un newtype pour Foo et Bar, puis dériver la mise en œuvre en fonction de la structure sous-jacente (dans ce cas, les listes) automatiquement -XGenerializedNewtypeDeriving
.
S'ils sont vraiment censés être structurellement similaire, mais vous avez codé d'une manière qu'aucun code peut être partagé, alors c'est un motif suspect.
Il est difficile de dire de votre exemple ce serait la bonne façon.
Pourquoi avez-vous besoin à la fois Foo
et Bar
si elles sont structurellement similaires? Tu ne peux pas aller juste avec Foo
?
Si, pour une raison quelconque, je ne vois pas,
vous avez besoin à la foisFoo
et Bar
vous devrez utiliser une classe de type mais vous pouvez nettoyer le code à l'aide de modèle Haskell.
Quelque chose comme
$(superDuper "Foo")
pourrait générer le code
data Foo = Foo [Param]
instance Parameterization Foo where
addParam (Foo params) param = Foo (param:params)
où
superDuper :: String -> Q [Dec]
superDuper n =
let name = mkName n
dataD = DataD [] name [] [NormalC name [] ] -- correct constructor here
instD = InstanceD [] ... -- add code here
return [dataD, instD]
Ce serait au moins se débarrasser du codage de la plaque de la chaudière.