17

我看到人们谈论在 Haskell 中废弃你的样板泛型编程。这些术语是什么意思?我什么时候想使用 Scrap Your Boilerplate,我该如何使用它?

4

1 回答 1

20

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

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

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

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

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

这个“引擎”是我们打算废弃的样板。


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

section :: Generic a => a -> AnnotatedTree

并且“有效”注释树可以投射回某些品牌的 ADT

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

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

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

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

这样,结合 final 和 initialsectionretracts 以及我们的注释树总是“有效”的不变量,我们有

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

做什么everywhere f a?它尝试f在 ADT 中应用“无处不在”的功能a。换句话说,我们现在将双重否定化简写成如下

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

换句话说,id只要 redex(Negate (Negate _))无法匹配,它就会起作用。如果我们适用everywhere于此

simplify :: Exp -> Exp
simplify = everywhere doubleNegSimpl

然后通过通用遍历“到处”消除双重否定。...样板已经消失了。

于 2015-01-31T02:24:10.257 回答