我看到有人在谈论 报废你的锅炉 和 通用编程 在哈斯克尔。这些术语是什么意思?我什么时候想要使用Scrap Your Boilerplate,我该如何使用它?
我看到有人在谈论 报废你的锅炉 和 通用编程 在哈斯克尔。这些术语是什么意思?我什么时候想要使用Scrap Your Boilerplate,我该如何使用它?
通常在对复杂数据类型进行转换时,我们只需要影响结构的小块 - 换句话说,我们只针对特定的可简化表达式,重新索引。
经典的例子是对一类整数表达式的双重否定消除:
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
和 retract
s和我们注释的树总是“有效”的不变量,我们有
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
然后通过一般遍历“双处”消除双重否定。该 ...
样板消失了。