通过特定节点上的更改来遍历表达式的遍历

时间:2014-12-29 19:34:48

标签: haskell

我正在阅读CIS194课程(2014年秋季版)和我的讲座 我希望对我的练习7的解决方案有一些评论 作业5。

以下是问题:

  

练习7(可选)distribute和squashMulId函数是   非常相似,因为他们遍历整个表达   对特定节点的更改。概括了这个概念,使这两个   函数可以只关注它们需要转换的位。

(你可以在这里看到整个作业:http://www.seas.upenn.edu/~cis194/fall14/hw/05-type-classes.pdf

以下是练习6中的squashMulId函数:

squashMulId :: (Eq a, Ring a) => RingExpr a -> RingExpr a
squashMulId AddId = AddId
squashMulId MulId = MulId
squashMulId (Lit n) = Lit n
squashMulId (AddInv x) = AddInv (squashMulId x)
squashMulId (Add x y) = Add (squashMulId x) (squashMulId y)
squashMulId (Mul x (Lit y))
    | y == mulId = squashMulId x
squashMulId (Mul (Lit x) y)
    | x == mulId = squashMulId y
squashMulId (Mul x y) = Mul (squashMulId x) (squashMulId y)

这是我对练习7的解决方案:

distribute :: RingExpr a -> RingExpr a
distribute = transform distribute'
    where distribute' (Mul x (Add y z)) = Just $ Add (Mul x y) (Mul x z)
          distribute' (Mul (Add x y) z) = Just $ Add (Mul x z) (Mul y z)
          distribute' _ = Nothing

squashMulId :: (Eq a, Ring a) => RingExpr a -> RingExpr a
squashMulId = transform simplifyMul
    where simplifyMul (Mul x (Lit y))
              | y == mulId = Just $ squashMulId x
          simplifyMul (Mul (Lit x) y)
              | x == mulId = Just $ squashMulId y
          simplifyMul _ = Nothing

transform :: (RingExpr a -> Maybe (RingExpr a)) -> RingExpr a -> RingExpr a
transform f e
    | Just expr <- f e = expr
transform _ AddId = AddId
transform _ MulId = MulId
transform _ e@(Lit n) = e
transform f (AddInv x) = AddInv (transform f x)
transform f (Add x y) = Add (transform f x) (transform f y)
transform f (Mul x y) = Mul (transform f x) (transform f y)

有没有更好的方法来进行这种概括?

1 个答案:

答案 0 :(得分:2)

您的transform函数是处理AST的一般转换的一个非常好的开始。我会告诉你一些与之相关的东西,这会更加普遍。

uniplate library定义了以下用于描述简单抽象语法树的类。类的实例只需要提供uniplate的定义,该定义应该对节点的直接后代执行转换步骤,可能带有副作用。

import Control.Applicative
import Control.Monad.Identity

class Uniplate a where
    uniplate :: Applicative m => a -> (a -> m a) -> m a

    descend :: (a -> a) -> a -> a
    descend f x = runIdentity $ descendM (pure . f) x

    descendM :: Applicative m => (a -> m a) -> a -> m a
    descendM = flip uniplate

为具有Uniplate实例的任何类型定义整个表达式的后序转换。

transform :: Uniplate a => (a -> a) -> a -> a
transform f = f . descend (transform f)

我对RingExprRing的定义的猜测(家庭作业练习的链接已被破坏)

data RingExpr a
    = AddId
    | MulId
    | Lit a
    | AddInv (RingExpr a)
    | Add (RingExpr a) (RingExpr a)
    | Mul (RingExpr a) (RingExpr a)
  deriving Show

class Ring a where
    addId  :: a
    mulId  :: a
    addInv :: a -> a
    add    :: a -> a -> a
    mul    :: a -> a -> a

我们可以为任何uniplate定义RingExpr a。对于具有子表达式的三个表达式AddInvAddMul,我们对每个子表达式执行转换p,然后将表达式重新放在{ {1}}使用Applicative<$>的中缀版本)和fmap。对于没有子表达式的其余表达式,我们只需使用<*>将它们打包回Applicative

pure

我们从instance Uniplate (RingExpr a) where uniplate e p = case e of AddInv x -> AddInv <$> p x Add x y -> Add <$> p x <*> p y Mul x y -> Mul <$> p x <*> p y _ -> pure e transform实例获取的Uniplate函数与您的完全相同,但RingExpr a返回类型不是必需的。一个不想改变表达式的函数可以简单地返回表达式。

这是我写作Maybe的动摇。我将文字拆分为一个单独的函数,以使事物看起来更清晰。

squashMulId

这适用于一个简单的例子

replaceIds :: (Eq a, Ring a) => RingExpr a -> RingExpr a
replaceIds (Lit n) | n == addId = AddId
replaceIds (Lit n) | n == mulId = MulId
replaceIds e                    = e

simplifyMul :: RingExpr a -> RingExpr a
simplifyMul (Mul x     MulId) = x
simplifyMul (Mul MulId x    ) = x
simplifyMul e                 = e

squashMulId :: (Eq a, Ring a) => RingExpr a -> RingExpr a
squashMulId = transform (simplifyMul . replaceIds)