我正在使用an AST和simple pattern matching遍历the Reader
monad。
在我的项目的其他地方,我已经定义了a walk
function来遍历AST,它本身使用foldl
来将访问树中每个节点的结果减少为单个幺半群结果(例如,来自树中特殊节点的to produce a "symbol table"。
我的问题是:是否可以将这两种方法结合起来并使用像walk
函数这样的函数:
walk :: Monoid a => (Node -> a) -> a -> Node -> a
walk f acc n = foldl (walk f) (acc <> f n) children
where
children = case n of
Blockquote b -> b
DocBlock d -> d
FunctionDeclaration {} -> functionBody n
List l -> l
ListItem i -> i
Paragraph p -> p
Unit u -> u
_ -> [] -- no Node children
和Reader
- 就像下面代码中的遍历(为简洁起见省略了一些位) - 同时?
markdown :: Node -> String
markdown n = runReader (node n) state
where state = State (getSymbols n) (getPluginName n)
node :: Node -> Env
node n = case n of
Blockquote b -> blockquote b >>= appendNewline >>= appendNewline
DocBlock d -> nodes d
FunctionDeclaration {} -> nodes $ functionBody n
Paragraph p -> nodes p >>= appendNewline >>= appendNewline
Link l -> link l
List ls -> nodes ls >>= appendNewline
ListItem l -> fmap ("- " ++) (nodes l) >>= appendNewline
Unit u -> nodes u
我在这里的使用动机是我的walk
函数已经编码了如何为每个模式获取子项以及如何执行AST的有序遍历的知识。我真的不想为每次遍历重新实现它,所以在更多地方使用walk
会更好,包括我需要使用Reader
的地方(以后可能会State
可能在堆栈中。)
这些事情可以有效地结合在一起吗?
答案 0 :(得分:2)
你的walk
函数看起来非常像Foldable
类,它是Traversable
的超类(dfeuer也在评论中指出)。在你的位置,我看看mono-traversable
package。
但我预感到,Node
类型实际上不会成为目前制定的良好MonoTraversable
实例。非正式的原因是这些概念之间没有很好的分离:
考虑Functor
和Traversable
课程的一种非常非正式的方式是他们对内容&#34;内容进行操作。没有改变的价值是&#34;结构&#34; (但是beware of taking that analogy too literally)。我考虑为您的MonoTraversable
类型编写Node
个实例,但经过一番思考后迅速退回:
{-# LANGUAGE TypeFamilies #-}
import Data.MonoTraversable
-- Here is the root of the problem. The type function
-- `Element` is supposed to pick out which pieces of a
-- `Node` are its "content" as opposed to its "structure,"
-- but with `Node` as formulated there isn't a distinction!
type instance Element Node = Node
鉴于此,otraverse
方法具有此主要类型:
otraverse :: Applicative f => (Element mono -> f (Element mono)) -> mono -> f mono
...在mono := Node
:
otraverse :: Applicative f => (Node -> f Node) -> Node -> f Node
...我担心有人可能会尝试使用丢弃根节点子节点的函数来调用otraverse
,例如,当某人尝试otraverse (return Separator)
时,它是多么邪恶?
我还没有检查过这些实例是否是非法的,所以我可能在此担心什么,如果我错了,值得检查一下。但它肯定会闻起来很糟糕&#34;根据我的设计意识。
那该怎么办?我将重点研究Node
类型,将其分为两种类型:
Content
类型,表示在从左到右走的每一步都可见的信息,但不表示Content
的嵌套方式; Structure
类型,用于定义树的可能形状 - Content
的嵌套方式。完成后,您就可以像这样重新制定walk
函数:
walk :: Monoid a => (Content -> a) -> a -> Structure -> a
请注意,此函数的第二个参数(初始acc :: Monoid a => a
值)是多余的,这两个函数看起来是可以确定的:
walk' :: Monoid a => (Content -> a) -> Structure -> a
walk' f = walk f mempty
walk :: Monoid a => (Content -> a) -> a -> Structure -> a
walk f acc tree = acc <> walk' f tree
我的walk'
签名看起来就像来自ofoldMap
的{{1}}方法:
mono-traversable
然后,您可以自然地询问是否可以为重新构造的类型实施ofoldMap :: Monoid m => (Element mono -> m) -> mono -> m
并获取我在上面提到的签名的MonoTraversable
操作,该操作专门用于otraverse
和{ {1}}和f := Reader r
将是:
mono := Structure