2012-02-14 22 views
8

No puedo dormir! :)Lazy vs Eager evaluación y la construcción de listas de doble enlace

He escrito un programa pequeño que construye una lista de enlaces dobles en Haskell. La propiedad del lenguaje básico para hacerla fue la evaluación perezosa (vea el grupo de código a continuación). Y mi pregunta es ¿puedo hacer lo mismo en un puro lenguaje funcional con evaluación ansioso o no? En cualquier caso, ¿qué propiedades ansioso lenguaje funcional deben tener para poder construir dicha estructura (impureza?)?

import Data.List 

data DLList a = DLNull | 
    DLNode { prev :: DLList a 
      , x :: a 
      , next :: DLList a 
      } 
    deriving (Show) 

walkDLList :: (DLList a -> DLList a) -> DLList a -> [a] 
walkDLList _ DLNull = [] 
walkDLList f [email protected](DLNode _ x _) = x : walkDLList f (f n) 

-- Returns first and last items. 
makeDLList :: [a] -> (DLList a, DLList a) 
makeDLList xs = let (first, last) = step DLNull xs in (first, last) 
    where 
    step prev [] = (DLNull, prev) 
         -- Here I use laziness. 'next' is not built yet, it's a thunk. 
    step prev (x : xs) = let this = DLNode prev x next 
          (next, last) = step this xs 
         in (this, last) 

testList :: [Int] -> IO() 
testList l = let 
    (first, last) = makeDLList l 
    byNext = walkDLList next first 
    byPrev = walkDLList prev last 
    in do 
    putStrLn $ "Testing: " ++ show l 
    print byNext 
    print byPrev 

main = do 
    testList [] 
    testList [1, 2, 3, 4] 
+0

echa un vistazo a [* this * code] (http://rosettacode.org/wiki/Doubly-Linked_List_%28traversal%29#Haskell). :) Devuelve solo el nodo principal, y es excepcionalmente simple. –

Respuesta

4

Mientras una lengua tiene algo así como cierres, etc. lambdas siempre se puede simular la pereza. Se podría reescribir ese código, incluso en Java (sin mutar las variables, etc), sólo tiene que envolver cada operación "perezosa" en algo así como

interface Thunk<A> { 
    A eval(); 
} 

Por supuesto, esto sería terrible, pero es posible.

+3

Necesita algo actualizable para obtener el efecto de evaluación diferida (evaluar como máximo una vez), de lo contrario, emulará la llamada por nombre. – augustss

+0

Entonces, agosto, tu punto es que no es suficiente solo cierres y lambdas? – demi

+2

Creo que solo se refiere a que el resultado de 'eval' debe almacenarse en caché, por lo que el procesador no se vuelve a calcular si' eval' se ejecuta varias veces. – danr

4

En el subconjunto de Prolog sin retrotracción, que se puede ver como explícitamente establecido una vez con un lenguaje funcional puro, puede compilar fácilmente las listas de doble enlace. Es la transparencia referencial que lo hace difícil en Haskell, ya que prohíbe la configuración explícita del Prolog , explícitamente variables aún no establecidas, y en su lugar obliga a Haskell a lograr el mismo efecto en la forma distorsionada de "atar" el nudo". Creo.

Además, en realidad no hay mucha diferencia entre recursividad vigilado de Haskell en evaluación perezosa vs listas abiertas de Prolog construidas en contras de módulo-cola recursividad moda. IMO. Aquí hay, por ejemplo, un ejemplo de lazy lists in Prolog. El almacenamiento compartido memorizado se utiliza como mediador de acceso universal, por lo que los resultados de los cálculos previos se pueden organizar para almacenarlos en caché.

Ahora que lo pienso, puede usar C de forma restrictiva como un lenguaje funcional puro y entusiasta, si nunca restablece ninguna variable ni ningún puntero una vez que está configurado. Aún tiene punteros nulos, al igual que Prolog tiene variables, por lo que también lo es, explícitamente establecido una vez. Y, por supuesto, puedes crear listas doblemente vinculadas con él.

Así que la única pregunta que queda es, ¿te admitirá dichas reglajes vez idiomas como pura?

6

Una lista doblemente enlazada se puede implementar de una manera puramente funcional en un lenguaje entusiasta como zipper en una lista de enlace único. Ver, por ejemplo, Rosetta Code > Doubly-linked list > OCaml > Functional.

+0

Creo que el punto de la lista doblemente * * vinculada es que ** ** no asigna almacenamiento nuevo en el recorrido, a diferencia del código al que se ha vinculado, Dan.En él, 'prev (next alist)' no es el mismo almacenamiento que 'alist', a diferencia del C habitual, por ejemplo, la implementación, y también el código Haskell de la pregunta anterior, donde después de' let (alist, zlist) = makeDLList xs 'tenemos' prev (next alist) == alist' y 'next (prev zlist) == zlist' donde' == 'significa *" el mismo almacenamiento real "*, o en Lisp-parlance *" 'eq'- equivalente "* (para' xs' no vacío, por supuesto). –

+0

@WillNess con respecto a "no asignar [...] nuevo almacenamiento en el cruce", que es un detalle de implementación y tiene un costo. No puede "insertar" en una lista inmutable doblemente enlazada (en el estilo dado por OP) sin hacer una copia de la lista completa. Bueno, normalmente puede salirse con la suya con menos copias debido a la pereza, pero el punto es que la nueva lista doblemente enlazada no puede compartir ningún nodo con la antigua lista doblemente enlazada. La implementación de la cremallera inmutable permite compartir más, aunque a costa de más almacenamiento requerido para el recorrido (pero no * mucho * más, de nuevo debido a la posibilidad de compartir). –

+0

@WillNess n.b. Haskell no ofrece garantías sobre 'eq'-ness. El recolector de basura paralelo de GHC saca provecho de esto, y puede * eliminar * compartir realmente lo que se espera que esté allí. Esto es raro, y apenas afecta el rendimiento, y debido a datos inmutables, nunca afectará sus resultados. –

Cuestiones relacionadas