¿Cómo la 'deforestación' elimina 'árboles' de un programa?


12

Creo que entiendo cómo la deforestación consume y produce una lista al mismo tiempo (desde una función de plegado y desplegado; vea esta buena respuesta en CodeReview aquí ), pero cuando comparé eso con la entrada de wikipedia sobre la técnica que habló sobre 'eliminar árboles 'de un programa.

Entiendo cómo se puede analizar un programa en un árbol de análisis sintáctico (¿es correcto?), Pero ¿cuál es el significado de este uso de la deforestación para algún tipo de simplificación (¿es así?) De los programas? ¿Y cómo lo haría con mi código?

Respuestas:


9

Parece que Yatima2975 ha cubierto sus dos primeras preguntas, intentaré cubrir la tercera. Para hacer esto, trataré un caso poco realista, pero estoy seguro de que podrás imaginar algo más realista.

Imagina que quieres calcular la profundidad del árbol binario completo de orden . El tipo de árboles binarios (sin etiqueta) es (en sintaxis Haskell):n

type Tree = Leaf | Node Tree Tree

n

full : Int -> Tree
full n | n == 0 = Leaf
full n = Node (full (n-1)) (full (n-1))

Y la profundidad de un árbol es calculada por

depth : Tree -> Int
depth Leaf = 0
depth (Node t1 t2) = 1 + max (depth t1) (depth t2)

depth (full n)nfulldepthdepth (full n)full_depth

full_depth : Int -> Int
full_depth n | n == 0 = 0
full_depth n = 1 + max (full_depth (n-1)) (full_depth (n-1))

Esto evita la asignación de memoria del árbol completo y la necesidad de realizar una coincidencia de patrones, lo que mejora en gran medida el rendimiento. Además, si agrega la optimización

max t t --> t

full_depth

El único compilador de la corriente principal que lleva a cabo la deforestación es automática GHC, y si no recuerdo mal, esto se realiza sólo cuando se componen incorporado en funciones (por razones técnicas).


Otorgado porque obtuve más de esta respuesta por la forma en que fue formulada que por las otras respuestas, a pesar de que esencialmente cubren el mismo territorio.
Cris Stringfellow

6

Primero, las listas son una especie de árboles. Si representamos una lista como una lista vinculada , es solo un árbol cuyo nodo tiene 1 o 0 descendientes.

Los árboles de análisis son solo una utilización de los árboles como estructura de datos. Los árboles tienen muchas aplicaciones en ciencias de la computación, incluida la clasificación, implementación de mapas, matrices asociativas, etc.

En general, la lista, los árboles, etc. son estructuras de datos recursivas: cada nodo contiene cierta información y otra instancia de la misma estructura de datos. El plegado es una operación sobre todas esas estructuras que transforma los nodos de forma recursiva a valores "de abajo hacia arriba". El despliegue es el proceso inverso, convierte los valores en nodos "de arriba abajo".

Para una estructura de datos dada, podemos construir mecánicamente sus funciones de plegado y desplegado.

Como ejemplo, tomemos listas. (Usaré Haskell para los ejemplos, ya que está escrito y su sintaxis es muy limpia). La lista es un final o un valor y una "cola".

data List a = Nil | Cons a (List a)

Ahora imaginemos que estamos doblando una lista. En cada paso, tenemos que plegar el nodo actual y ya hemos plegado sus subnodos recursivos. Podemos representar este estado como

data ListF a r = NilF | ConsF a r

donde res el valor intermedio construido al plegar la sublista. Esto nos permite expresar una función de plegado sobre listas:

foldList :: (ListF a r -> r) -> List a -> r
foldList f Nil            = f NilF
foldList f (Cons x xs)    = f (ConsF x (foldList f xs))

Convertimos Listen ListFplegando recursivamente sobre su sublista y luego usamos una función definida en ListF. Si lo piensa, esta es solo otra representación del estándar foldr:

foldr :: (a -> r -> r) -> r -> List a -> r
foldr f z = foldList g
  where
    g NilF          = z
    g (ConsF x r)   = f x r

Podemos construir unfoldListde la misma manera:

unfoldList :: (r -> ListF a r) -> r -> List a
unfoldList f r = case f r of
                  NilF        -> Nil
                  ConsF x r'  -> Cons x (unfoldList f r')

De nuevo, es solo otra representación de unfoldr:

unfoldr :: (r -> Maybe (a, r)) -> r -> [a]

(Tenga en cuenta que Maybe (a, r)es isomorfo a ListF a r).

Y también podemos construir una función de deforestación:

deforest :: (ListF a r -> r) -> (s -> ListF a s) -> s -> r
deforest f u s = f (map (deforest f u) (u s))
  where
    map h NilF        = NilF
    map h (ConsF x r) = ConsF x (h r)

Simplemente omite el intermedio Listy fusiona las funciones de plegado y desplegado.

El mismo procedimiento se puede aplicar a cualquier estructura de datos recursiva. Por ejemplo, un árbol cuyos nodos pueden tener 0, 1, 2 o descendientes con valores en nodos de ramificación 1 o 0:

data Tree a = Bin (Tree a) (Tree a) | Un a (Tree a) | Leaf a

data TreeF a r = BinF r r | UnF a r | LeafF a

treeFold :: (TreeF a r -> r) -> Tree a -> r
treeFold f (Leaf x)       = f (LeafF x)
treeFold f (Un x r)       = f (UnF x (treeFold f r))
treeFold f (Bin r1 r2)    = f (BinF (treeFold f r1) (treeFold f r2))

treeUnfold :: (r -> TreeF a r) -> r -> Tree a
treeUnfold f r = case f r of
                  LeafF x         -> Leaf x
                  UnF x r         -> Un x (treeUnfold f r)
                  BinF r1 r2      -> Bin (treeUnfold f r1) (treeUnfold f r2)

Por supuesto, podemos crear deforestTreetan mecánicamente como antes.

(Por lo general, nos expresamos treeFoldmás convenientemente como:

treeFold' :: (r -> r -> r) -> (a -> r -> r) -> (a -> r) -> Tree a -> r

)

Dejaré de lado los detalles, espero que el patrón sea obvio.

Ver también:


Gran respuesta, gracias. Los enlaces y el ejemplo detallado son valiosos.
Cris Stringfellow

3

Es un poco confuso, pero la deforestación se aplica (en tiempo de compilación) para eliminar los árboles intermedios que se crearían (en tiempo de ejecución). La deforestación no implica cortar partes del árbol de sintaxis abstracta (eso es eliminación de ramas muertas :-)

Otra cosa que puede haberte dejado fuera es que las listas son árboles, ¡solo muy desequilibradas!


Ah, sí. Muy desequilibrado!
Cris Stringfellow
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.