【发布时间】:2014-08-18 02:24:13
【问题描述】:
我正在尝试回答this stackoverflow question, using uniplate as I suggested,但the only solution I've come up with so far 很丑。
这似乎是一个相当普遍的问题,所以我想知道是否有更优雅的解决方案。
基本上,我们有一个 GADT,它可以解析为 Expression Int 或 Expression Bool(忽略 codataIf = If (B True) codataIf codataIf):
data Expression a where
I :: Int -> Expression Int
B :: Bool -> Expression Bool
Add :: Expression Int -> Expression Int -> Expression Int
Mul :: Expression Int -> Expression Int -> Expression Int
Eq :: Expression Int -> Expression Int -> Expression Bool
And :: Expression Bool -> Expression Bool -> Expression Bool
Or :: Expression Bool -> Expression Bool -> Expression Bool
If :: Expression Bool -> Expression a -> Expression a -> Expression a
并且(在我的问题版本中)我们希望能够使用一个简单的操作从下到上评估表达式树,将叶子组合成一个新的叶子:
step :: Expression a -> Expression a
step = \case
Add (I x) (I y) -> I $ x + y
Mul (I x) (I y) -> I $ x * y
Eq (I x) (I y) -> B $ x == y
And (B x) (B y) -> B $ x && y
Or (B x) (B y) -> B $ x || y
If (B b) x y -> if b then x else y
z -> z
我在使用DataDeriving 派生Uniplate 和Biplate 实例时遇到了一些困难(这可能应该是一个危险信号),所以
我为Expression Int、Expression Bool 和Biplate 实例滚动了我自己的Uniplate 实例(Expression a) (Expression a)、(Expression Int) (Expression Bool) 和(Expression Bool) (Expression Int)。
这让我想出了这些自下而上的遍历:
evalInt :: Expression Int -> Expression Int
evalInt = transform step
evalIntBi :: Expression Bool -> Expression Bool
evalIntBi = transformBi (step :: Expression Int -> Expression Int)
evalBool :: Expression Bool -> Expression Bool
evalBool = transform step
evalBoolBi :: Expression Int -> Expression Int
evalBoolBi = transformBi (step :: Expression Bool -> Expression Bool)
但由于它们中的每一个都只能进行一次转换(结合Int 叶子或Bool 叶子,但两者都不能),它们无法完成完整的简化,而必须手动链接在一起:
λ example1
If (Eq (I 0) (Add (I 0) (I 0))) (I 1) (I 2)
λ evalInt it
If (Eq (I 0) (I 0)) (I 1) (I 2)
λ evalBoolBi it
If (B True) (I 1) (I 2)
λ evalInt it
I 1
λ example2
If (Eq (I 0) (Add (I 0) (I 0))) (B True) (B False)
λ evalIntBi it
If (Eq (I 0) (I 0)) (B True) (B False)
λ evalBool it
B True
我的 hackish 解决方法是为 Either (Expression Int) (Expression Bool) 定义一个 Uniplate 实例:
type WExp = Either (Expression Int) (Expression Bool)
instance Uniplate WExp where
uniplate = \case
Left (Add x y) -> plate (i2 Left Add) |* Left x |* Left y
Left (Mul x y) -> plate (i2 Left Mul) |* Left x |* Left y
Left (If b x y) -> plate (bi2 Left If) |* Right b |* Left x |* Left y
Right (Eq x y) -> plate (i2 Right Eq) |* Left x |* Left y
Right (And x y) -> plate (b2 Right And) |* Right x |* Right y
Right (Or x y) -> plate (b2 Right Or) |* Right x |* Right y
Right (If b x y) -> plate (b3 Right If) |* Right b |* Right x |* Right y
e -> plate e
where i2 side op (Left x) (Left y) = side (op x y)
i2 _ _ _ _ = error "type mismatch"
b2 side op (Right x) (Right y) = side (op x y)
b2 _ _ _ _ = error "type mismatch"
bi2 side op (Right x) (Left y) (Left z) = side (op x y z)
bi2 _ _ _ _ _ = error "type mismatch"
b3 side op (Right x) (Right y) (Right z) = side (op x y z)
b3 _ _ _ _ _ = error "type mismatch"
evalWExp :: WExp -> WExp
evalWExp = transform (either (Left . step) (Right . step))
现在我可以做完整的简化了:
λ evalWExp . Left $ example1
Left (I 1)
λ evalWExp . Right $ example2
Right (B True)
但是为了完成这项工作,我必须做大量的error 和包装/解包,这让我觉得这不雅和错误。
有没有正确的方法来解决这个问题uniplate?
【问题讨论】:
-
您正在使用的
Expression类型可以在没有GADT 的情况下表示,此时解决方案要简单得多。您的 AST 中是否特别需要 GADT? -
Stephen Diehl:我能想到的非 GADT 版本似乎很冗长。你在想什么?
-
例如为什么 GADT 在封闭数据类型上,例如:
data Expr = I Int | B Bool | Add Expr Expr ...? -
Stephen Diehl:但这允许 GADT 不允许的非法表达式
Add (B True) (B True) -
@ThreeFx 呵呵呵呵。人们应该看加里伯恩哈特的Wat?!如果他们还没有,请谈谈。