问题 什么是“废弃锅炉”?


我看到有人在谈论 报废你的锅炉 和 通用编程 在哈斯克尔。这些术语是什么意思?我什么时候想要使用Scrap Your Boilerplate,我该如何使用它?


11118
2018-01-30 20:32


起源

谷歌有很多文件...... - Justin Pihony
@JustinPihony我问了这个问题,因为Stack Overflow的目标之一就是得到答案 所有 编程问题, 甚至那些可以用Google搜索的东西。就我所见,这个问题以前从未被问过,我相信它会产生一些高质量的答案。 - Benjamin Hodgson♦
论文的第2部分 废弃样板 - 通用编程的实用设计模式 包含一个很好的激励例子。 - ErikR
FWIW,我经常想知道这个问题的答案是什么。 SYBP的文档庞大而复杂,似乎省略了描述它实际尝试的问题 解决... - MathematicalOrchid


答案:


通常在对复杂数据类型进行转换时,我们只需要影响结构的小块 - 换句话说,我们只针对特定的可简化表达式,重新索引。

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

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

值得注意的是,我正在介绍 Generic 类型类,表示具有的类型 section 和 retract 定义。

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

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

这样,结合最终和初始 section 和 retracts和我们注释的树总是“有效”的不变量,我们有

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

然后通过一般遍历“双处”消除双重否定。该 ... 样板消失了。


12
2018-01-31 02:24



如何 everywhere 弄清楚模式是否成功匹配?通常,非穷举模式会抛出必须捕获的异常 IO。 - Benjamin Hodgson♦
它没有。 doubleNegSimpl 是redex不匹配时的身份,所以 everywhere 字面上只是应用它 到处 在大多数地方它只是一个无操作。当然, everywhere 小心谨慎地做到这一点。 - J. Abrahamson
哦,当然。我显然没有仔细阅读;) - Benjamin Hodgson♦