Haskell:从leafs到root的递归

时间:2018-03-18 12:53:43

标签: haskell

我有一个基本数学算术表达式的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(从下到上)的给定表达式?

2 个答案:

答案 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

与使用直接递归调用的代码相比,它具有以下优点:

  • 递归被抽象出cata函数,而不是与简化逻辑交织在一起。
  • 您不会忘记在子表达式上调用简化。
  • Catamorphisms从下到上工作。
  • 加法和乘法的简化可以用不同的函数编写。
  • 如果必须扩展AST(例如添加新的构造函数),维护代码要容易得多。

如果您想了解有关递归方案的更多信息,请阅读此blog post series