Interakcja z użytkownikiem i IO

Kompilacja programów i monada IO

Programy w języku Haskell można kompilowac do postaci uruchamialnej, niewymagajacej interpretera. Polcenie do kompilacji:

ghc -o executable_name source_file.hs

Kod, który ma się wykonać po uruchomieniu programu wrzucamy do funkcji main:

main :: IO ()
main = putStrLn "Hello, World!"

I kompilacja:

ghc -o hello hello.hs
./hello

Zwróć uwagę na nagłówek funkcji main, która jest typu IO i zwraca () – coś na wzór void. IO to specjalna wbudowana monada, co to naprawdę jest monada, dowiemy się w kolejnym odcinku, na dzisiaj wystarczy nam uznać, że monada to jakaś wartość opakowana w kontekst. Inne przykład opakowania (monady), który już znamy, to Maybe, które opakowuje wartość w sposób umożliwiający wyrazić brak wartości. W przypadku IO, opakowane są wszystkie wartości pochodzące spoza programu - innymi słowy wejście i wyjście. Funkcja main jest tak naprawdę akcją wejścia-wyjścia, jedynym miejscem w programie, który ma bezpośredni kontakt ze światem zewnętrznym, mając dostęp do monady IO. Funkcja main może przekazywać tę monadę innym funkcjom lub sama wypakowywać wartości z monady i przekazywać je dalej.

Przykładem wypakowania wartości z monady IO jest funkcja wczytująca napis z klawiatury, zdefiniowana jako:

getLine' :: IO String

Innymi słowy, jest to akcja typu IO i zwraca String. Akcjami będziemy dzisiaj nazywać funkcje operujące na monadach.

Interakcja z użytkownikiem

Przykład najprostszej interakcji z użytkownikiem:

main = do
    putStrLn "Hello, what's your name?"
    name <- getLine
    putStrLn $ "Hello " ++ name ++ " good to see you!"

Spotykamy tutaj nową rzecz: do. Nie jest to jednak żaden nowy operator, tylko syntaktyczny sugar dla łączenia akcji w ciąg o ustalonej kolejności przy pomocy operatora `»=` i `»` Uwaga Każda z linii w bloku do musi być IO akcją.

Na przykład alternatywnym zapisem dla powyższego z wykorzystaniem operatorów `»` i `»=` jest kod poniżej:

main =   putStrLn "Hello, what's your name?" >> (  getLine  >>= (\ name ->  putStrLn $ "Hello " ++ name ++ " good to see you!"))

» łączy akcje, gdzie wynik pierwszej akcji nie ma wpływu na drugą. »= natomiast przekazuje wynik jednej akcji do kolejnej. Oba te operatory umożliwiają łańcuchowanie akcji - notacja do sprawia, że kod wygląda czytelniej.

Uwaga na return

Spójrz na kod poniżej i zastanów się, co powinno się wydarzyć. Następwnie skompiluj, uruchom i zweryfikuj swoje podejrzenia.

main = do
    putStrLn "Hello, what's your name?"
    return ()
    name <- getLine
    putStrLn $ "Hello " ++ name ++ " good to see you!"

Polecenie return w bloku do nie kończy wcale wykonywania kodu i nie powoduje zwrócenia wartości na zewnątrz bloku. Jest ono traktowane jak kolejna akcja, która w przypadku powyżej nie robi nic. Co naprawdę znaczy return w haskellu, dowiemy się na następnych zajęciach.

Pliki

Czytanie z plików pokazano na przykładzie poniżej (zakładajac, ze istnieje jakiś plik 'machine.txt', np:

machine.txt
Welcome my son, welcome to the machine
Where have you been?
It's alright we know where you've been
You've been in the pipeline, filling in time
Provided with toys and 'scouting for boys'
You brought a guitar to punish your ma
And you didn't like school, and you
Know you're nobody's fool
So welcome to the machine
Welcome my son, welcome to the machine
What did you dream?
It's alright we told you what to dream
You dreamed of a big star
He played a mean guitar
He always ate in the Steak Bar
He loved to drive in his Jaguar
So welcome to the machine
import System.IO  
 
main = do  
    handle <- openFile "machine.txt" ReadMode  
    contents <- hGetContents handle  
    putStr contents  
    hClose handle

Jak widzisz uzyskanie uchwytu do pliku odbywa się za pomocą funkcji `openFile`, o sygnaturze:

openFile :: FilePath -> IOMode -> IO Handle

Gdzie `FilePath` to po prostu datatype będący synonimem Stringa, IOMode to typ opisany jak poniżej:

data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode  

Zwracana jest natomiast akcja IO będąca uchwytem do pliku.

Funkcja `hGetContents` realizuje lazy IO, czyli nie wczytuje całego pliku do pamięci, tylko czyta go kiedy jest to potrzebne. Istnieje też wersja do realizowania lazy IO w terminalu (poczytaj o getContents). W przypadku, jeśli chcemy wczytać całośc pliku do pamięci, bez lazy IO, możemy użyc `readFile`:

import System.IO  
 
main = do  
    contents <- readFile "machine.txt"  
    putStr contents

Pisanie do pliku jest wykonywane w analogiczny sposób:

import System.IO     
 
main = do     
    contents <- readFile "machine.txt"     
    writeFile "new_machine.txt" contents 

Zadania

  1. Napisz grę „Guess a number”, gdzie system losuje losową liczbę od 0-10, a użytkownik ma 3 próby odgadnięcia tej liczby, za każdym razem otrzymując podpowiedź, że wpisana liczba była mniejsza, lub większa od szukanej. Aby wygenerować losową liczbę, użyj:
    import System.Random
     
    main = do
    -- ''randomIO'' odwołuje się do świata zewnętrznego, używając go jako ziarna do generacji losowej liczby.
        num <- randomIO :: IO Int
        print num
  2. Napisz program odwracający i wyświetlający napis, który wcześniej został podany przez użytkownika. Program powinien wczytywać i odwracać stringi do czasu, aż użytkownik poda pustą linię.
  3. Napisz program wczytujący plik i zapisujący jego zawartość w wersji CAPSLOCK
  4. Napisz program typu spellchecker, czytający plik tekstowy, znajdujący w nim słowa, które nie występują w /usr/share/dict/words. Sprawdzanie nie powinno być case-sensitive. Znalezione błędne słowa powinny zostać zapisane w odrębnym pliku.
pl/dydaktyka/pp/haskell/lab-io.txt · ostatnio zmienione: 2020/04/27 08:50 przez msl
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0