使用镜头,cosmosOf,uniplate和State monad来提取有关AST的信息

时间:2016-06-08 02:11:07

标签: haskell lens

the following code使用an AST遍历cosmosOfuniplate寻找特定类型的节点。对于它找到的任何内容,它会在记录中设置Bool标记,该标记在the lens package的帮助下使用State monad进行传播。

这一切都有效,但感觉相当严厉。感觉就像镜头,State monad,可能cosmosOf / uniplate可能都是矫枉过正。是否有更好或更惯用的方法来做到这一点?

{-# 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

2 个答案:

答案 0 :(得分:4)

您可以通过Uniplate模块para完成您想要做的事情。

基本上para聚合从节点及其子节点收集的信息,并将其传递给节点的父节点以进行进一步聚合。

这是您示例的简化版本 - 我们确定Node是否包含CommandAnnotation和/或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

uniplate github repo中的README.md通过示例对库进行了很好的概述。

为了提高效率,您可以使用bitset包来设置。

答案 1 :(得分:1)

您似乎在询问如何最好地为自定义树实现walk函数。这可以使用简单的递归来完成,这并不依赖于任何库。另一方面,根据ADT的大小,它可以输入更多内容。但是,如果他们不需要安装镜头等,您图书馆的消费者将会感激不尽。

有关示例,请参阅pandoc's walk functions以遍历其AST

  • walk :: (a -> a) -> b -> b只需修改树,而
  • walkM :: (Monad m, Functor m) => (a -> m a) -> b -> m b是monadic,所以另外你可以保持状态,就像你询问的布尔值一样。这没有什么不妥,而且州monad实际上是为这样的案件而建的。