我有一个基本数学算术表达式的AST:
data Expr = Constant Int
| Variable String
| Add Expr Expr
| Multiply Expr Expr
deriving (Show)
我还有一个非常简单的函数,它简化了给定的表达式:
simplify :: Expr -> Expr
simplify (Add (Constant 0) e) = simplify e
simplify (Add e (Constant 0)) = simplify e
simplify (Add (Constant a) (Constant b)) = Constant (a + b)
simplify (Add e1 e2) = Add (simplify e1) (simplify e2)
simplify (Multiply (Constant 0) _) = Constant 0
simplify (Multiply _ (Constant 0)) = Constant 0
simplify (Multiply (Constant 1) e) = e
simplify (Multiply e (Constant 1)) = e
simplify (Multiply (Constant a) (Constant b)) = Constant (a * b)
simplify (Multiply e1 e2) = Multiply (simplify e1) (simplify e2)
simplify e = e
不幸的是,这个函数不是很有效,因为它简化了从根到叶子的表达式(从上到下)。考虑一下这个表达式:
exampleExpr :: Expr
exampleExpr = Add
(Multiply (Constant 1) (Variable "redrum"))
(Multiply (Constant 0) (Constant 451))
需要两次函数调用(simplify (simplify exampleExpr)
)才能将此表达式缩减为Variable "redrum"
。使用自下而上的方法,它应该只花费一个函数调用。
我还没有足够的经验能够有效地编写此代码。所以我的问题是:如何重写此函数以简化从leafs到root(从下到上)的给定表达式?
答案 0 :(得分:5)
首先,您错过了几个递归调用。在这些方面:
simplify (Multiply (Constant 1) e) = e
simplify (Multiply e (Constant 1)) = e
您应该用simplify e
替换右侧。
simplify (Multiply (Constant 1) e) = simplify e
simplify (Multiply e (Constant 1)) = simplify e
现在从下往上重写表达式。问题在于,您需要在等式的左侧寻找简化模式,即在简化子项之前。您需要先简化子项,然后查找模式。
simplify :: Expr -> Expr
simplify (Add x y) =
case (simplify x, simplify y) of
(Constant 0, e) -> e
(e, Constant 0) -> e
(Constant a, Constant b) -> Constant (a + b)
(x1, y1) -> Add x1 y1
simplify (Multiply x y) =
case (simplify x, simplify y) of
(Constant 0, _) -> Constant 0
(_, Constant 0) -> Constant 0
(Constant 1, e) -> e
(e, Constant 1) -> e
(Constant a, Constant b) -> Constant (a * b)
(x1, y1) -> Multiply x1 y1
simplify e = e
在等式的左侧,我们找到当前节点的子节点。在右边,我们在简化的孩子中寻找模式。改进此代码的一种方法是分离查找和替换子项以及匹配简化模式的两个职责。这是递归替换Expr
的每个子树的一般函数:
transform :: (Expr -> Expr) -> Expr -> Expr
transform f (Add x y) = f $ Add (transform f x) (transform f y)
transform f (Multiply x y) = f $ Multiply (transform f x) (transform f y)
transform f e = f e
transform
采用(非递归)转换函数,该函数计算单节点模式的替换,并以自下而上的方式递归地将其应用于树中的每个节点。要编写转换函数,只需查找有趣的模式,忘记递归重写子项。
simplify = transform f
where
f (Add (Constant 0) e) = e
f (Add e (Constant 0)) = e
f (Add (Constant a) (Constant b)) = Constant (a + b)
f (Multiply (Constant 0) _) = Constant 0
f (Multiply _ (Constant 0)) = Constant 0
f (Multiply (Constant 1) e) = e
f (Multiply e (Constant 1)) = e
f (Multiply (Constant a) (Constant b)) = Constant (a * b)
f e = e
由于f
的参数已经由transform
重写了其子项,因此我们不需要详尽地匹配每个可能的模式或明确地通过该值进行递归。我们寻找我们关心的那些,并且不需要转换的节点会落入所有f e = e
案例中。
像lens
' s Plated
module这样的通用编程库采用transform
之类的编程模式,并使它们具有通用性。您(或编译器)编写了少量代表数据类型形状的代码,并且库一次性实现递归的高阶函数,如transform
。
答案 1 :(得分:1)
简化表达式AST是称为 catamorphism 的递归方案的典型应用程序。以下是Edwald Kmett的recursion-schemes库的示例:
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE TemplateHaskell #-}
module CataExprSimplify where
import Data.Functor.Foldable
import Data.Functor.Foldable.TH
data Expr = Constant Int
| Variable String
| Add Expr Expr
| Multiply Expr Expr
deriving (Show)
-- | Generate the base functor
makeBaseFunctor ''Expr
simplify :: Expr -> Expr
simplify = cata $ algSimplAdd . project . algSimplMult
-- | Simplify Addition
simplZero :: Expr -> Expr
simplZero = cata algSimplAdd
algSimplAdd :: ExprF Expr -> Expr
algSimplAdd (AddF (Constant 0) r) = r
algSimplAdd (AddF l (Constant 0)) = l
algSimplAdd (AddF (Constant l) (Constant r)) = Constant (l + r)
algSimplAdd x = embed x
-- | Simplify Multiplication
simplMult :: Expr -> Expr
simplMult = cata algSimplMult
algSimplMult :: ExprF Expr -> Expr
algSimplMult (MultiplyF (Constant 1) r) = r
algSimplMult (MultiplyF l (Constant 1)) = l
algSimplMult (MultiplyF (Constant 0) _) = Constant 0
algSimplMult (MultiplyF _ (Constant 0)) = Constant 0
algSimplMult (MultiplyF (Constant l) (Constant r)) = Constant (l * r)
algSimplMult x = embed x
与使用直接递归调用的代码相比,它具有以下优点:
如果您想了解有关递归方案的更多信息,请阅读此blog post series