2010-05-06 6 views
10

Próbuję uzyskać głębsze zrozumienie lenistwa w Haskell.Jak lenistwo i I/O współpracują ze sobą w Haskell?

wyobrażałem sobie następujący fragment dzisiaj:

data Image = Image { name :: String, pixels :: String } 

image :: String -> IO Image 
image path = Image path <$> readFile path 

Odwołanie jest to, że może po prostu utworzyć instancję obrazu i przekazać ją wokół; jeśli muszę danych obrazowych byłoby odczytane leniwie - jeśli nie, czas i pamięć koszt odczytu pliku będzie unikać:

main = do 
    image <- image "file" 
    putStrLn $ length $ pixels image 

Ale jest to, że, jak to faktycznie działa? W jaki sposób lenistwo jest zgodne z IO? Czy będzie to readFile, niezależnie od tego, czy uzyskam dostęp do pixels image, czy też środowisko wykonawcze pozostawi to, co tajne, jeśli nigdy go nie odtworzę?

Jeśli obraz rzeczywiście czyta się leniwie, to czy nie jest możliwe wykonanie operacji we/wy poza kolejnością? Na przykład, co jeśli natychmiast po wywołaniu image mogę usunąć plik? Teraz wywołanie putStrLn nie znajdzie niczego podczas próby odczytu.

Odpowiedz

17

Jak lenistwo jest kompatybilne z I/O?

Krótka odpowiedź: Nie jest.


Długa odpowiedź: IO działania są ściśle zsekwencjonowano dla dość dużo powodów myślisz. Wszelkie czyste obliczenia wykonane z wynikami mogą oczywiście być leniwe; na przykład, jeśli czytasz w pliku, wykonujesz trochę przetwarzania, a następnie drukujesz niektóre wyniki, prawdopodobnie żadne przetwarzanie, które nie jest potrzebne przez dane wyjściowe, nie zostanie ocenione. Jednak cały plik zostanie odczytany, nawet części, z których nigdy nie korzystasz. Jeśli chcesz leniwy I/O, masz około dwie opcje:

  • przewróceniu własne jawnych procedur leniwy załadunku i takie, jak chcesz w dowolnej ścisłym języku. Wydaje się denerwujące, przyznane, ale z drugiej strony Haskell tworzy delikatny, imperatywny język. Jeśli chcesz spróbować czegoś nowego i interesującego, spróbuj spojrzeć na Iteratees.

  • Cheat like a cheating cheater. Funkcje such as hGetContents wykonają dla ciebie leniwą operację wejścia/wyjścia na żądanie, bez żadnych pytań. Jaki jest haczyk? To (technicznie) przełamuje przejrzystość referencyjną. Czysty kod może pośrednio wywoływać efekty uboczne, a śmieszne rzeczy mogą się dzielić, polegające na zamawianiu efektów ubocznych, jeśli twój kod jest naprawdę zawiły. hGetContents i przyjaciele są zaimplementowane using unsafeInterleaveIO, co jest ... dokładnie to, co mówi na puszce. Prawdopodobieństwo wysadzenia w twarz nie jest tak duże, jak przy użyciu unsafePerformIO, ale uważaj się za ostrzeżonego.

+1

Dzięki za tę odpowiedź! W rzeczywistości to opis HGetContents z RWH mnie wprawił w zakłopotanie. Nie zdawałem sobie sprawy, że to szczególny przypadek i użyłem pod tym kątem niebezpiecznych połączeń IO. Zasadniczo mój przykład odczytuje plik, gdy tylko przetwarzane jest działanie readFile? Wydaje się to o wiele bardziej spójne, jeśli tak. – Bill

+5

@Bill: Oto implementacja readFile, prosto ze standardowych bibliotek GHC: 'readFile name = openFile nazwa ReadMode >> = hGetContents' Więc nie, twój przykład należy do kategorii" oszust oszustów ". Powiedział, że leniwy funkcje I/O są zwykle wystarczająco bezpieczne dla większości codziennych praktycznych zastosowań, więc nie przejmuj się zbytnio, chyba że czystość jest dla ciebie bardzo ważna. –

+1

Wiem, że Oleg mówi "unsafeInterleaveIO' łamie przejrzystość referencyjną, ale nie zgadzam się. Powiedziałbym, że jest to po prostu niedeterministyczne, jak wiele rzeczy w monadzie "IO". Czy funkcja 'getCurrentTime' przełamuje przezroczystość referencyjną, ponieważ mogę jej użyć do określenia, która z dwóch zewnętrznych funkcji jest zaimplementowana bardziej efektywnie? –

9

Lazy I/O łamie czystość Haskella. Wyniki z readFile są rzeczywiście produkowane leniwie, na żądanie. Kolejność działań wejścia/wyjścia nie jest ustalona, ​​więc tak, mogą wystąpić "nieczynne". Problem usunięcia pliku przed wyciągnięciem pikseli jest prawdziwy. Krótko mówiąc, leniwy I/O jest wielką wygodą, ale jest to narzędzie o bardzo ostrych krawędziach.

Książka o świecie rzeczywistym Haskell ma numer lengthy treatment of lazy I/O i omawia niektóre z pułapek.