2017-02-01 3 views
1

J'ai un certain type de données qui est très similaire aux arbres ordinaires, juste une forme spécialisée. Je souhaite initialiser des instances de mon type de données en utilisant impérativement des lentilles. Voici ce que je suis maintenant:Des moyens pratiques pour initialiser des champs imbriqués avec des lentilles

example :: NestedTree 
example = flip execState (NT 0 mempty) $ do 
    dummy .= 3 
    tree.at "foo" ?= flip execState (NT 0 mempty) (dummy .= 10) 

Vous pouvez observer dans cet exemple que je peux remplacer la première (NT 0 mempty) avec (NT 3 mempty) mais ce n'est pas le point. Ce que je veux, c'est aussi pouvoir initialiser HashMap s imbriqués en utilisant ce joli style impératif. Plus précisément, je veux être en mesure d'écrire quelque chose comme ceci:

example :: NestedTree 
example = flip execState (NT 0 mempty) $ do 
    dummy .= 3 
    tree.at "foo" ?= flip execState (NT 0 mempty) $ do 
     dummy .= 10 
     tree.at "foo nested" ?= NT 5 mempty 
    tree.at "bar" ?= flip execState (NT 0 mempty) $ do 
     dummy .= 15 
     tree.at "bar nested" ?= NT (-3) mempty 

Ma vraie structure de données est plus complexe et il devient très vite vraiment moche pour l'initialiser à l'aide des enregistrements de simples. Donc, je veux utiliser une sorte de DSL, et les lentilles correspondent assez bien à mon besoin. Mais vous pouvez remarquer que le code ci-dessus ne compile pas. C'est parce que ($) a la plus basse priorité et je ne peux pas écrire simplement tree.at "foo" ?= flip execState (NT 0 mempty) $ do. Mais je ne veux vraiment pas ajouter () autour imbriqués do s.

Existe-t-il de bons moyens de mélanger des opérateurs arbitraires avec $ et do afin d'écrire de telles fonctions? Je ne veux pas introduire une aide comme wordsAssign = (?=) et les fonctions appel comme

wordsAssign (tree.at "foo") $ flip execState (NT 0 mempty) $ do

parce que j'aime opérateur ?=. Peut-être que je fais tout faux et ce genre de choses que je veux faire peut être fait sans lentilles avec certains opérateurs écrits à la main?

Répondre

3

zoom est fait sur mesure pour gérer les mises à jour d'état imbriquées. Dans votre cas, malheureusement, la Maybe ness fait l'utiliser un peu maladroit:

example :: NestedTree 
example = flip execState (NT 0 mempty) $ do 
    dummy .= 3 
    zoom (tree.at "foo") $ do 
     put (Just (NT 0 mempty)) 
     _Just.dummy .= 10 
     _Just.tree.at "foo nested" ?= NT 5 mempty 
    -- Or, using zoom one more time: 
    zoom (tree.at "bar") $ do 
     put (Just (NT 0 mempty)) 
     zoom _Just $ do 
      dummy .= 15 
      tree.at "bar nested" ?= NT (-3) mempty 

Par souci de comparaison, si vous ne l'avez pas besoin d'insérer de nouvelles clés au niveau extérieur, vous seriez en mesure d'utiliser ix au lieu de at déposer tous les Maybe boilerplate concernant la PI:

zoom (tree.ix "foo") $ do 
     dummy .= 10 
     tree.at "foo nested" ?= NT 5 mempty 
+2

Merci! J'ai essayé cette approche et cela fonctionne comme par magie. Si toute personne intéressée: la fonction d'utilité pour me aider avec le zoom (https://github.com/serokell/log-warper/blob/91d82ed1b3a331ed4a8e0f458390f985028d1817/src/System/Wlog/LoggerConfig.hs#L120) et voici comment je l'utilise (https : //github.com/serokell/log-warper/blob/91d82ed1b3a331ed4a8e0f458390f985028d1817/test/Test/Wlog/RollingSpec.hs#L69) – Shersh

2

Vous pouvez définir votre propre ?= avec la même priorité que $ pour les faire jouer ensemble agréable:

import Control.Lens hiding ((?=)) 
import qualified Control.Lens as L 

(?=) 
    :: MonadState s m 
    => ASetter s s a (Maybe b) -> b -> m() 
(?=) = (L.?=) 

infixr 0 ?= 

Avec ce votre exemple fonctionne.