2013-09-05 3 views
8

Pour la représentation d'un arbre de syntaxe DSL, j'ai des types de données qui représentent cet arbre. À plusieurs endroits, au sein de cet arbre, je reçois un certain nombre de sous-éléments qui sont facultatifs et/ou qui ont une multiplicité de «*». Donc, un type de données pourrait ressembler à quelque chose commeHaskell: Définition d'une interface appropriée pour les types de données avec de nombreux champs

data SpecialDslExpression = MyExpression String [Int] Double [String] (Maybe Bool) 

Ce que je cherche est la possibilité de construire un tel type sans avoir à spécifier tous les paramètres, en supposant que j'ai un défaut valable pour chacun d'eux. Le scénario d'utilisation est tel que j'ai besoin de créer de nombreuses instances du type avec toutes sortes de combinaisons de paramètres donnés ou omis (la plupart du temps deux ou trois), mais très rarement tous. Le regroupement des paramètres en sous-types ne m'amènera pas loin car les combinaisons de paramètres ne suivent pas un modèle qui aurait une amélioration de la segmentation.

Je pourrais définir des fonctions avec différentes combinaisons de paramètres pour créer le type en utilisant les valeurs par défaut pour le reste, mais je pourrais en retrouver un certain nombre qui deviendraient difficile à nommer correctement, car il pourrait ne pas être possible de donner un nom propre à l'idée de createWithFirstAndThirdParameter dans un contexte donné. Donc, à la fin, la question se résume à: est-il possible de créer un tel type de données ou une abstraction qui me donnerait quelque chose comme des paramètres facultatifs que je peux spécifier ou omettre à souhait?

+3

Enregistrements + une instance par défaut – jozefg

+0

Peut-être que votre expression DSL pourrait devenir un monoid? – bennofs

Répondre

11

D'accord, je développerai réellement mon commentaire. Premièrement, définissez votre type de données comme un enregistrement (et ajoutez quelques types de synonymes).

data Example = E { 
    one :: Int, 
    two :: String, 
    three :: Bool, 
    four :: Double 
} 

suivant la création d'une instance par défaut

defaultExample = Example 1 "foo" False 1.4 

puis lorsqu'un utilisateur souhaite modifier un champ dans le défaut de faire leurs propres données, ils peuvent le faire:

myData = defaultExample{four=2.8} 

Enfin, quand ils veulent correspondre à un seul élément, ils peuvent utiliser

foo MyData{four=a} = a 
+8

Il existe une classe de type, Data.Default, qui définit la méthode 'def' qui renvoie l'enregistrement 'default' de votre type. Vous pouvez alors généralement faire 'def {field = value}' pour tous vos types d'enregistrements. –

12

Je suggère une combinaison d'objectifs et une instance par défaut. Si vous n'avez pas déjà importé Control.Lens dans la moitié de vos modules, il est temps de commencer! Que diable sont les lentilles, de toute façon? Une lentille est un getter et un setter écrasé en une seule fonction. Et ils sont très composables. Chaque fois que vous avez besoin d'accéder ou de modifier des parties d'une structure de données, mais que vous pensez que la syntaxe d'enregistrement est lourde, les objectifs sont là pour vous.

Donc, la première chose que vous devez faire - activer TH et importer Control.Lens.

{-# LANGUAGE TemplateHaskell #-} 

import Control.Lens 

La modification que vous devez faire à votre type de données est l'ajout de noms pour tous les champs, comme ceci:

data SpecialDslExpression = MyExpression { _exprType :: String 
             , _exprParams :: [Int] 
             , _exprCost :: Double 
             , _exprComment :: [String] 
             , _exprLog :: Maybe Bool 
             } deriving Show 

Les underscores au début des noms de champs sont importants, pour ce qui suit étape. Parce que maintenant nous voulons générer des lentilles pour les champs. Nous pouvons demander à GHC de faire cela pour nous avec Template Haskell.

$(makeLenses ''SpecialDslExpression) 

Ensuite, la dernière chose à faire est de construire une instance "vide".Prenez garde que personne ne vérifie statiquement que vous remplissez réellement tous les champs requis, donc vous devriez de préférence ajouter un error à ces champs pour que vous ayez au moins une erreur d'exécution. Quelque chose comme ceci:

emptyExpression = MyExpression (error "Type field is required!") [] 0.0 [] Nothing 

Maintenant vous êtes prêt à rouler! Vous ne pouvez pas utiliser un emptyExpression, et il échouera au moment de l'exécution:

> emptyExpression 
MyExpression {_exprType = "*** Exception: Type field is required! 

Mais! Tant que vous remplissez le champ de type, vous serez d'or:

> emptyExpression & exprType .~ "Test expression" 

MyExpression { _exprType = "Test expression" 
      , _exprParams = [] 
      , _exprCost = 0.0 
      , _exprComment = [] 
      , _exprLog = Nothing 
      } 

Vous pouvez également remplir plusieurs champs à la fois, si vous voulez.

> emptyExpression & exprType .~ "Test expression" 
|     & exprLog .~ Just False 
|     & exprComment .~ ["Test comment"] 

MyExpression { _exprType = "Test expression" 
      , _exprParams = [] 
      , _exprCost = 0.0 
      , _exprComment = ["Test comment"] 
      , _exprLog = Just False 
      } 

Vous pouvez également utiliser des lentilles pour appliquer une fonction à un champ, ou regarder à l'intérieur d'un champ d'un champ, ou modifier toute autre expression existante et ainsi de suite. Je recommande vraiment de jeter un oeil à ce que vous pouvez faire!

Questions connexes