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):norte
type Tree = Leaf | Node Tree Tree
norte
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)
d e p t h ( f u l l n) norteFu l ld e p t hdepth (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).
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".
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
donde
r
es el valor intermedio construido al plegar la sublista. Esto nos permite expresar una función de plegado sobre listas:Convertimos
List
enListF
plegando recursivamente sobre su sublista y luego usamos una función definida enListF
. Si lo piensa, esta es solo otra representación del estándarfoldr
:Podemos construir
unfoldList
de la misma manera:De nuevo, es solo otra representación de
unfoldr
:(Tenga en cuenta que
Maybe (a, r)
es isomorfo aListF a r
).Y también podemos construir una función de deforestación:
Simplemente omite el intermedio
List
y 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:
Por supuesto, podemos crear
deforestTree
tan mecánicamente como antes.(Por lo general, nos expresamos
treeFold
más convenientemente como:)
Dejaré de lado los detalles, espero que el patrón sea obvio.
Ver también:
fuente
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!
fuente