====== 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: 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 ===== - 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 - 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ę. - Napisz program wczytujący plik i zapisujący jego zawartość w wersji CAPSLOCK - 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.