【问题标题】:Is it possible to compare two trees with recursion schemes?是否可以将两棵树与递归方案进行比较?
【发布时间】:2016-07-23 13:55:16
【问题描述】:

我有这个 AST

data ExprF r = Const Int | Add   r r
type Expr = Fix ExprF

我想比较一下

x = Fix $ Add (Fix (Const 1)) (Fix (Const 1))
y = Fix $ Add (Fix (Const 1)) (Fix (Const 2))

但所有递归方案函数似乎只适用于单一结构

显然我可以使用递归

eq (Fix (Const x)) (Fix (Const y)) = x == y
eq (Fix (Add x1 y1)) (Fix (Add x2 y2)) = (eq x1 x2) && (eq y1 y2)
eq _ _ = False

但我希望可以使用某种 zipfold 功能。

【问题讨论】:

  • 你从哪里得到你的修复?
  • 你可能想要一个 zygohistomorphic prepromorphism。我不知道它做了什么,但有了这样的名字,我无法想象它不能做的事情太多了。 :)
  • 在递归方案中修复有 Eq1 的实例

标签: haskell tree abstract-syntax-tree catamorphism recursion-schemes


【解决方案1】:

作用于单个参数的递归方案就足够了,因为我们可以从方案应用程序返回一个函数。在这种情况下,我们可以从Expr 上的方案应用程序返回一个Expr -> Bool 函数。为了有效的相等检查,我们只需要paramorphisms:

{-# language DeriveFunctor, LambdaCase #-}

newtype Fix f = Fix (f (Fix f))
data ExprF r = Const Int | Add r r deriving (Functor, Show)
type Expr = Fix ExprF

cata :: Functor f => (f a -> a) -> Fix f -> a
cata f = go where go (Fix ff) = f (go <$> ff)

para :: Functor f => (f (Fix f, a) -> a) -> Fix f -> a
para f (Fix ff) = f ((\x -> (x, para f x)) <$> ff)

eqExpr :: Expr -> Expr -> Bool
eqExpr = cata $ \case
  Const i -> cata $ \case
    Const i' -> i == i'
    _        -> False
  Add a b -> para $ \case
    Add a' b' -> a (fst a') && b (fst b')
    _         -> False

当然,catapara 方面是可以轻松实现的:

cata' :: Functor f => (f a -> a) -> Fix f -> a
cata' f = para (\ffa -> f (snd <$> ffa)

从技术上讲,几乎所有有用的功能都可以使用cata 实现,但它们不一定有效。我们可以使用cata实现para

para' :: Functor f => (f (Fix f, a) -> a) -> Fix f -> a
para' f = snd . cata (\ffa -> (Fix (fst <$> ffa) , f ffa))

但是,如果我们在eqExpr 中使用para',我们会得到二次复杂度,因为para' 在输入的大小上始终是线性的,而我们可以使用para 来查看最顶部的Expr 值在恒定时间内。

【讨论】:

  • 是否可以像cataZipWith :: Fix f -&gt; Fix f -&gt; (f a -&gt; f c -&gt; a) -&gt; a 一样编写eqExpr 的多态版本?
  • @András Kovács 在eqExpr 的实现中,为什么模式匹配后面的目录/参数是必要的?我们不能直接在第二棵树上进行模式匹配吗?
  • @danidiaz 我认为我们只能使用递归方案。
  • @danidiaz 这也违背了bananas and lenses 的精神。
【解决方案2】:

(此响应使用 data-fix 库,因为我无法让 recursion-schemes 进行编译。)

我们可以将两棵树的差异建模为基于原始函子的“差异函子”的变形或展开。

考虑以下类型

data DiffF func r = Diff (Fix func) (Fix func) 
                  | Nodiff (func r)
                  deriving (Functor)

type ExprDiff = Fix (DiffF ExprF) 

这个想法是 ExprDiff 将遵循原始 Expr 树的“公共结构”,只要它保持相等,但在遇到差异的那一刻,我们切换到 Diff 叶,即存储我们发现不同的两个子树。

实际的比较函数是:

diffExpr ::  Expr -> Expr -> ExprDiff  
diffExpr e1 e2 = ana comparison (e1,e2)
    where
    comparison :: (Expr,Expr) -> DiffF ExprF (Expr,Expr)
    comparison (Fix (Const i),Fix (Const i')) | i == i' = 
        Nodiff (Const i')
    comparison (Fix (Add a1 a2),Fix (Add a1' a2')) = 
        Nodiff (Add (a1,a1') (a2,a2'))
    comparison (something, otherthing) = 
        Diff something otherthing

变形的“种子”是我们要比较的一对表达式。

如果我们只是想要一个谓词Expr -&gt; Expr -&gt; Bool,我们稍后可以使用检测Diff 分支存在的变态。

【讨论】:

    猜你喜欢
    • 2017-05-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-19
    • 1970-01-01
    • 2023-03-20
    • 1970-01-01
    • 2011-05-07
    相关资源
    最近更新 更多