recursivos recursivas potencia listas funciones ejercicios ejemplos ciclos haskell tree abstract-syntax-tree catamorphism recursion-schemes

haskell - recursivas - ¿Es posible comparar dos árboles con esquemas de recursión?



listas recursivas en haskell (2)

Los esquemas de recursión que actúan en un único argumento son suficientes, porque podemos devolver una función desde una aplicación de esquema. En este caso, podemos devolver una función Expr -> Bool desde una aplicación de esquema en Expr . Para una comprobación de igualdad eficiente, solo necesitamos paramorfismos:

{-# 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

Por supuesto, cata es trivialmente aplicable en términos de para :

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

Técnicamente, casi todas las funciones útiles son implementables usando cata , pero no son necesariamente eficientes. Podemos implementar para usar cata :

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

Sin embargo, si usamos para'' en eqExpr obtenemos complejidad cuadrática, ya que para'' siempre es lineal en el tamaño de la entrada, mientras que podemos usar para para Expr valores Expr más altos en tiempo constante.

Tengo este AST

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

y quiero comparar

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

Pero todas las funciones de esquemas de recursión parecen funcionar solo con una sola estructura

Obviamente puedo usar recursión

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

Pero espero que sea posible utilizar algún tipo de función de cierre automático.


(Esta respuesta usa la biblioteca de corrección de datos porque no pude compilar los esquemas de recursión ).

Podemos modelar la diferencia de dos árboles como un anamorfismo o despliegue de un "functor de diferencias" que se basa en el functor original.

Considera los siguientes tipos

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

La idea es que ExprDiff seguirá la "estructura común" de los árboles Expr originales, siempre y cuando se mantenga igual, pero en el momento en que se encuentra una diferencia, cambiamos a la hoja Diff , que almacena los dos subárboles que encontramos ser diferente.

La función de comparación real sería:

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

La "semilla" del anamorfismo es el par de expresiones que queremos comparar.

Si simplemente queremos un predicado Expr -> Expr -> Bool podemos usar un catamorfismo que detecta la presencia de ramas Diff .