对于我正在研究的辅助项目,我目前必须处理抽象语法树并根据规则进行转换(细节不重要)。
AST本身并不重要,这意味着它具有仅限于某些类型的子表达式。 (例如,运算符A
必须只接受B
类型的参数,而不是任何Expr
。我的数据类型的简化缩减版本如下所示:
data Expr = List [Expr]
| Strange Str
| Literal Lit
data Str = A Expr
| B Expr
| C Lit
| D String
| E [Expr]
data Lit = Int Int
| String String
我的目标是分解显式递归并依赖递归方案,如these two优秀博客文章所示,它提供了非常强大的通用工具来操作我的AST。应用必要的因子,我们最终得到:
data ExprF a = List [a]
| Strange (StrF a)
| Literal (LitF a)
data StrF a = A a
| B a
| C (LitF a)
| D String
| E [a]
data LitF a = Int Int
| String String
如果我没有陷入困境,type Expr = Fix ExprF
现在应该与先前定义的Expr
同构。
但是,为这些案例编写cata
变得相当繁琐,因为我必须在B a :: StrF a
内为Str :: ExprF a
模式匹配cata
才能打字。对于整个原始AST,这是不可行的。
我偶然发现fixing GADTs,在我看来它似乎是我的问题的解决方案,但重复的高阶类型等用户不友好的界面是非常必要的样板。
所以,总结一下我的问题:
Functor
更好的支持吗?答案 0 :(得分:2)
如果您已经完成了在数据类型中分离递归的工作,那么您可以只导出Functor
并完成。您不需要任何花哨的功能来获得递归方案。 (作为旁注,没有理由参数化Lit
数据类型。)
折叠是:
newtype Fix f = In { out :: f (Fix f) }
gfold :: (Functor f) => (f a -> a) -> Fix f -> a
gfold alg = alg . fmap (gfold alg) . out
要指定代数(alg
参数),您需要针对ExprF
进行案例分析,但另一种方法是让折叠具有十几个或更多参数:每个参数一个数据构造函数。这不会真正为你节省很多打字,而且阅读起来会更难。如果你想要(并且这通常需要排名2类型),你可以将所有这些参数打包到一个记录中然后你可以使用记录更新来更新在各种情况下提供“默认”行为的“预制”记录。有一篇旧论文Dealing with Large Bananas采用了这样的方法。我要建议的是,清楚地说,只是将上面的gfold
函数包含在一个记录的函数中,并传入一个代数,该代数将进行案例分析并为每个函数调用相应的记录字段。情况下。
当然,你可以使用GHC Generics或the various "generic/polytypic" programming libraries来代替Scrap Your Boilerplate。你基本上是在重新创造他们所做的事情。