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.
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.
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.
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
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
/usr/share/dict/words
. Sprawdzanie nie powinno być case-sensitive. Znalezione błędne słowa powinny zostać zapisane w odrębnym pliku.