使用foldl和没有样板的Reader monad递归地走AST

时间:2016-04-05 05:27:39

标签: haskell monads fold parsec reader-monad

我正在使用an ASTsimple 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可能在堆栈中。)

这些事情可以有效地结合在一起吗?

1 个答案:

答案 0 :(得分:2)

你的walk函数看起来非常像Foldable类,它是Traversable的超类(dfeuer也在评论中指出)。在你的位置,我看看mono-traversable package

但我预感到,Node类型实际上不会成为目前制定的良好MonoTraversable实例。非正式的原因是这些概念之间没有很好的分离:

  1. AST的结构:哪些节点是其子节点。
  2. AST的内容:&#34;属性&#34;在每个节点。
  3. 考虑FunctorTraversable课程的一种非常非正式的方式是他们对内容&#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类型,将其分为两种类型:

    1. 一种Content类型,表示在从左到右走的每一步都可见的信息,但不表示Content的嵌套方式;
    2. Structure类型,用于定义树的可能形状 - Content的嵌套方式。
    3. 完成后,您就可以像这样重新制定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