什么是 "Scrap Your Boilerplate"?

What is "Scrap Your Boilerplate"?

我看到人们在 Haskell 中谈论 Scrap Your Boilerplategeneric programming。这些术语是什么意思?我什么时候想使用 Scrap Your Boilerplate,我该如何使用它?

通常在对复杂数据类型进行转换时,我们只需要影响结构的一小部分——换句话说,我们只针对特定的可简化表达式、redexes。

经典的例子是对一类整数表达式的双重否定消除:

data Exp = Plus Exp Exp | Mult Exp Exp | Negate Exp | Pure Int

doubleNegSimpl :: Exp -> Exp
doubleNegSimpl (Negate (Negate e)) = e
...

即使在描述这个例子时,我也不想写出整个 ... 部分。它完全是机械的——只不过是在整个 Exp.

中继续递归的引擎

这个 "engine" 是我们打算废弃的样板文件。


为了实现这一点,Scrap Your Boilerplate 提出了一种机制,我们可以通过该机制构建 "generic traversals" 数据类型。这些遍历在完全不知道所讨论的特定数据类型的情况下完全正确地运行。为此,非常粗略地,我们有一个通用注释树的概念。这些比 ADT 大,所有 ADT 都可以投影到带注释的树的类型中:

section :: Generic a => a -> AnnotatedTree

和 "valid" 注释树可以投影回某些品牌的 ADT

retract :: Generic a => AnnotatedTree -> Maybe a

值得注意的是,我引入了 Generic 类型类来指示定义了 sectionretract 的类型。

使用这种通用的、带注释的所有数据类型的树表示,我们可以一劳永逸地定义遍历。特别是,我们提供了一个接口(有策略地使用 sectionretract),以便最终用户永远不会接触到 AnnotatedTree 类型。相反,它看起来有点像:

everywhere' :: Generic a => (a -> a) -> (AnnotatedTree -> AnnotatedTree)

这样,结合最终和初始 sectionretracts 以及我们的注释树总是 "valid" 的不变量,我们有

everywhere :: Generic a => (a -> a) -> (a -> a)
everywhere f a0 = fromJust . retract . everywhere' f . section

everywhere f a 是做什么的?它尝试在 ADT a 中应用函数 f "everywhere"。换句话说,我们现在将双重否定简化写成如下

doubleNegSimpl :: Exp -> Exp
doubleNegSimpl (Negate (Negate e)) = e
doubleNegSimpl e                   = e

换句话说,只要 redex (Negate (Negate _)) 匹配失败,它就充当 id。如果我们将 everywhere 应用于此

simplify :: Exp -> Exp
simplify = everywhere doubleNegSimpl

然后通过泛型遍历消除双重否定"everywhere"。 ... 样板文件不见了。