2016-06-08 6 views
2

J'ai the following code qui traverse an AST en utilisant cosmosOf et uniplate en recherchant des nœuds d'un certain type. Pour tout ce qu'il trouve, il définit un drapeau Bool dans un enregistrement qui est propagé en utilisant une monade State à l'aide de the lens package.Utilisation de la lentille, cosmosOf, uniplate, et l'état Monad pour extraire des informations sur un AST

Tout cela fonctionne, mais se sent assez lourd. Il se sent comme des lentilles, la monade State, et peut-être cosmosOf/uniplate peut-être être exagéré ici. Y a-t-il une façon meilleure ou plus idiomatique de le faire?

{-# LANGUAGE FlexibleContexts #-} 
{-# LANGUAGE TemplateHaskell #-} 

module Docvim.Visitor.Section (getSectionInfo) where 

import Control.Lens 
import Control.Monad.State 
import Data.Data.Lens (uniplate) 
import Docvim.AST 

data SectionInfo = SectionInfo { _hasCommand :: Bool 
           , _hasCommands :: Bool 
           , _hasFunction :: Bool 
           , _hasFunctions :: Bool 
           , _hasMapping :: Bool 
           , _hasMappings :: Bool 
           , _hasOption :: Bool 
           , _hasOptions :: Bool 
           } deriving (Show) 

type Env = State SectionInfo 

makeLenses ''SectionInfo 

defaultSectionInfo :: SectionInfo 
defaultSectionInfo = SectionInfo { _hasCommand = False 
           , _hasCommands = False 
           , _hasFunction = False 
           , _hasFunctions = False 
           , _hasMapping = False 
           , _hasMappings = False 
           , _hasOption = False 
           , _hasOptions = False 
           } 

getSectionInfo :: Node -> SectionInfo 
getSectionInfo n = execState (mapMOf_ (cosmosOf uniplate) check n) defaultSectionInfo 
    where 
    check (CommandAnnotation {}) = hasCommand .= True 
    check CommandsAnnotation  = hasCommands .= True 
    check (FunctionAnnotation _) = hasFunction .= True 
    check FunctionsAnnotation = hasFunctions .= True 
    check (MappingAnnotation _) = hasMapping .= True 
    check MappingsAnnotation  = hasMappings .= True 
    check (OptionAnnotation {}) = hasOption .= True 
    check OptionsAnnotation  = hasOptions .= True 
    check _      = modify id 

Répondre

4

Ce que vous voulez faire peut être accompli par para à partir du module Uniplate.

Fondamentalement, para regroupe les informations collectées à partir d'un noeud et de ses enfants et les transmet au parent du noeud pour une agrégation supplémentaire.

Voici une version simplifiée de votre exemple - on détermine si oui ou non un noeud contient CommandAnnotation et/ou noeuds FunctionAnnotation

import Data.Monoid 
import qualified Data.Set as Set 
import qualified Data.Generics.Uniplate.Data as Uniplate 
import Data.Data 

... 

data HasSection = HasCommandAnnotation | HasFunction | HasOther 
    deriving (Show,Read,Enum,Bounded,Ord,Eq) 

toHas :: Node -> HasSection 
toHas (CommandAnnotation {}) = HasCommandAnnotation 
toHas (FunctionsAnnotation {}) = HasFunction 
toHas _      = HasOther 

getSectionInfo :: Node -> Set.Set HasSection 
getSectionInfo n = Uniplate.para visit n 
    where visit n res = Set.singleton (toHas n) <> mconcat res 

Le README.md au repo github uniplaque a une bonne vue d'ensemble de la bibliothèque avec des exemples.

Pour une meilleure efficacité, vous pouvez utiliser le package bitset pour les jeux.

+1

Il existe également un [para] (https://hackage.haskell.org/package/lens/docs/Control-Lens-Plated.html#v:para) pour 'Plated', l'équivalent de' Uniplate' dans le paquet de lentilles. – Cirdec

1

Il semble que vous demandez sur la meilleure façon de mettre en œuvre la fonction walk pour votre arbre personnalisé. Cela peut être fait en utilisant une simple récursion, qui ne dépend d'aucune bibliothèque. D'un autre côté, en fonction de la taille de vos ADT, il est beaucoup plus typé. Mais les consommateurs de votre bibliothèque seront reconnaissants si elles ne doivent pas installer des lentilles etc.

Pour un bon exemple, voir pandoc's walk functions pour traverser la AST:

  • walk :: (a -> a) -> b -> b modifie simplement l'arbre, tandis que
  • walkM :: (Monad m, Functor m) => (a -> m a) -> b -> m b est monadique, de sorte que vous pouvez par exemple conserver l'état autour, comme les valeurs booléennes que vous avez posées. Il n'y a rien de mal à cela et la monade d'état a été construite pour des cas comme celui-ci.