我正在阅读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)
有没有更好的方法来进行这种概括?
答案 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)
我对RingExpr
和Ring
的定义的猜测(家庭作业练习的链接已被破坏)
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
。对于具有子表达式的三个表达式AddInv
,Add
和Mul
,我们对每个子表达式执行转换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)