使用 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
包。
我有 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
包。