我看到人们在Haskell中谈论 Scrap Your Boilerplate 和泛型编程。这些术语是什么意思?我什么时候想要使用Scrap Your Boilerplate,我该如何使用它?
答案 0 :(得分:13)
通常在对复杂数据类型进行转换时,我们只需要影响结构的一小部分 - 换句话说,我们只针对特定的可简化表达式,重新索引,而单独使用。
经典的例子是对一类整数表达式的双重否定消除:
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
以及我们注释的树总是"有效"的不变量,我们有
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
换句话说,只要redex id
无法匹配,它就会充当(Negate (Negate _))
。如果我们将everywhere
应用于此
simplify :: Exp -> Exp
simplify = everywhere doubleNegSimpl
然后双重否定将被消除"无处不在"通过一般遍历。 ...
样板已消失。