2012-02-14 2 views
8

Я не могу спать! :)Lazy vs нетерпеливая оценка и построение двойного списка

Я написал небольшую программу, создав двойной связанный список в Haskell. Свойство основного языка, чтобы сделать это, было ленивой оценкой (см. Кучу кода ниже). И мой вопрос: могу ли я сделать то же самое в чистом функциональном языке с eager Оценка или нет? В любом случае, какие свойства eager функциональный язык должен иметь возможность построить такую ​​структуру (примесь?)?

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

проверить [* этот * код] (http://rosettacode.org/wiki/Doubly-Linked_List_%28traversal%29#Haskell). :) Он возвращает только головной узел и исключительно прост. –

ответ

4

Поскольку язык имеет что-то вроде закрытия, лямбда и т. Д., Вы всегда можете имитировать ленивость. Вы можете переписать этот код даже в Java (без мутирует переменные и т.д.), вам просто нужно обернуть каждую «ленивую» операцию в чем-то вроде

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

Конечно, это будет выглядеть ужасно, но это возможно.

+3

Вам нужно что-то обновляемое, чтобы получить эффект ленивой оценки (оценивать не чаще одного раза), иначе вы будете эмулировать вызов по имени. – augustss

+0

Итак, август, ваше дело в том, что недостаточно просто закрыть и лямбды? – demi

+2

Я думаю, он просто ссылается на то, что результат 'eval' должен быть кэширован, поэтому thunk не пересматривается, если' eval' запускается несколько раз. – danr

4

В подмножестве non-backtracking в Prolog, который можно увидеть как явно установленный чистый функциональный язык, вы можете легко создавать двусвязные списки. Это ссылочная прозрачность, которая делает ее трудной в Haskell, поскольку она запрещает явную настройку Prolog с именем, явно еще не установленных логических переменных и вместо этого вынуждает Haskell добиваться такого же эффекта в искаженном виде «привязки» узел". Я думаю.

Плюса, там на самом деле не так много различий между охраняемой рекурсией Haskell под ленивыми вычислениями против открытых списков Пролога, построенных в хвостовых рекурсий по модулю против моды. ИМО. Вот, например, пример lazy lists in Prolog. Памятное разделяемое хранилище используется как универсальный посредник доступа, поэтому результаты предыдущих вычислений могут быть организованы для кэширования.

Подумайте об этом, вы можете использовать C ограничительным образом как чистый чистый функциональный язык, если вы никогда не сбросите какую-либо переменную и никакой указатель после ее установки. У вас все еще есть нулевые указатели, так же как и у Prolog есть переменные, так что это тоже, явно задано-. И, конечно же, вы можете создавать с ним списки с двойной связью.

Так что единственный вопрос, который остается, как вы признать такие набор однократно языков в качестве чистого?

6

Двухсвязный список может быть реализован в чисто функциональном виде на нетерпеливом языке как zipper в односвязном списке. См., Например, Rosetta Code > Doubly-linked list > OCaml > Functional.

+0

Я думаю, что пункт дважды - * связанный * список состоит в том, что он делает ** не ** выделяет новое хранилище на обход, в отличие от кода, с которым вы связались, Дэн.В нем 'prev (next alist)' - это не то же самое хранилище, что и 'alist', в отличие от обычного C, скажем, реализации, а также кода Haskell из вопроса выше, где после' let (alist, zlist) = makeDLList xs 'у нас есть' prev (next alist) == alist' и 'next (prev zlist) == zlist' где' == 'означает *" то же самое фактическое хранилище * *, либо в Lisp-parlance * "' eq'- эквивалент "* (для непустых' xs', конечно). –

+0

@WillNess относительно «не выделять новое хранилище на обход», то есть деталь реализации, и приходит к стоимости. Вы не можете «вставить» в неизменяемый дважды связанный список (в стиле, данном OP), не делая копию всего списка. Как правило, вы избегаете меньшего копирования из-за лени, но дело в том, что новый список с двойной связью не может делиться ни с узлами со старым двусвязным списком. Непревзойденная реализация застежки-молнии позволяет увеличить общий доступ, хотя за счет большего объема хранения, необходимого для обхода (но не намного больше), опять же из-за способности делиться ими. –

+0

@WillNess n.b. Haskell не дает никаких гарантий относительно 'eq'-ness. Аналогичный сборщик мусора GHC использует это и может фактически * удалить * общий доступ, который вы, возможно, ожидаете. Это редкость и едва влияет на производительность, и из-за неизменяемых данных это никогда не повлияет на ваши результаты. –

Смежные вопросы