使用 lens、cosmosOf、uniplate 和 State monad 来提取有关 AST 的信息

Using lens, cosmosOf, uniplate, and the State monad to extract info about an AST

我有 the following code that traverses an AST using cosmosOf and uniplate looking for nodes of a certain type. For any that it finds, it sets a Bool flag in a record that is propagated using a State monad with the help of the lens package.

这一切都有效,但感觉相当笨拙。感觉像镜头、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

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

有关很好的示例,请参阅 pandoc's walk functions to traverse its AST:

  • walk :: (a -> a) -> b -> b 只是修改树,而
  • walkM :: (Monad m, Functor m) => (a -> m a) -> b -> m b 是单子的,因此您还可以保持状态,例如您询问的布尔值。这并没有错,state monad 实际上就是为这种情况而构建的。

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

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

这是您示例的简化版本 - 我们确定节点是否包含 CommandAnnotation and/or 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 存储库中的 README.md 通过示例很好地概述了库。

为了提高效率,您可以对集合使用 bitset 包。