在 haskell 中遍历树的最简单方法

The simplest way to generically traverse a tree in haskell

假设我使用 language-javascript 库在 Haskell 中构建 AST。 AST 有不同类型的节点,每个节点可以有这些不同类型的字段。 并且每种类型都可以有许多构造函数。 (所有类型实例化 DataEqShow)。

我想计算每个类型的构造函数在树中的出现次数。我可以使用 toConstr 来获取构造函数,理想情况下我会首先创建一个 Tree -> [Constr] 函数(然后计数很容易)。

有多种方法可以做到这一点。显然模式匹配太冗长了(想象大约 3 种类型和 9-28 个构造函数)。

所以想用泛型遍历,在SYB库中找了解决方法

  1. 有一个 everywhere 函数,它不适合我的需要,因为我不需要 Tree -> Tree 转换。
  2. gmapQ,从类型上看似乎很合适,但事实证明它不是递归的。
  3. 目前最可行的选择是 everywhereM。它仍然进行无用的转换,但我可以使用 Writer 来收集 toConstr 结果。不过,这种方式感觉不太对。

是否有替代方案不会执行无用的(对于此任务)转换并仍然提供构造函数列表? (它们在树中的出现顺序暂时无关紧要)

不确定这是否是最简单的,但是:

> data T = L | B T T deriving Data
> everything (++) (const [] `extQ` (\x -> [toConstr (x::T)])) (B L (B (B L L) L))
[B,L,B,B,L,L,L]

这里++说的是如何合并子项的结果。

const [] 是非 T 类型的子项的基本情况。对于 T 类型的那些,我们应用 \x -> [toConstr (x::T)].

如果您有多种树类型,则需要使用

扩展查询
const [] `extQ` (handleType1) `extQ` (handleType2) `extQ` ...

这是确定我们想要获取构造函数的类型所必需的。如果有很多类型,可能可以通过某种方式缩短它。

请注意,上面的代码在大树上不是很有效,因为以这种方式使用 ++ 会导致二次复杂度。在性能方面,return a Data.Map.Map Constr Int 会更好。 (即使我们确实需要为此定义一些 Ord Constr

Data.Generics.Uniplate.Data 模块中的

universe 可以为您提供相同类型的所有子树的列表。所以使用 Ilya 的例子:

data T = L | B T T deriving (Data, Show)

tree :: T
tree = B L (B (B L L) L)
λ> import Data.Generics.Uniplate.Data
λ> universe tree
[B L (B (B L L) L),L,B (B L L) L,B L L,L,L,L]
λ> fmap toConstr $ universe tree
[B,L,B,B,L,L,L]