我the following code使用an AST遍历cosmosOf
,uniplate
寻找特定类型的节点。对于它找到的任何内容,它会在记录中设置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
答案 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实际上是为这样的案件而建的。