我看到人们谈论在 Haskell 中废弃你的样板和泛型编程。这些术语是什么意思?我什么时候想使用 Scrap Your Boilerplate,我该如何使用它?
1 回答
通常在对复杂数据类型进行转换时,我们只需要影响结构的一小部分——换句话说,我们只针对特定的可简化表达式,即 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
值得注意的是,我引入了Generic
typeclass 来指示具有section
和retract
定义的类型。
使用所有数据类型的这种通用的、带注释的树表示,我们可以一劳永逸地定义遍历。特别是,我们提供了一个界面(使用section
和retract
战略性),以便最终用户永远不会接触到该AnnotatedTree
类型。相反,它看起来有点像:
everywhere' :: Generic a => (a -> a) -> (AnnotatedTree -> AnnotatedTree)
这样,结合 final 和 initialsection
和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
然后通过通用遍历“到处”消除双重否定。...
样板已经消失了。