Spis treści

Lab: Wprowadzenie do Haskella

Tematyka:

GHCi

ghci jest interaktywnym środowiskiem, które pozwala na wykonywanie wyrażeń Haskella w trybie interaktywnym.
ghc jest natomiast kompilatorem języka Haskell.

:help    		    wyświetla pomoc wewnątrz GHCi                               (:h,:?)
:quit                       wyjście z GHCi
:load [*]<module> ...       załaduj moduły/skrypty <module>... do GHCi                  (:l)
                            np. można załadować nasz kod:
                            $Prelude>:l hello.hs
:reload                     przeładuj załadowane moduły (pomocne przy zmianie źródeł	(:r)
:			    powtórz ostatnie polecenie GHCi
:browse [<module>]          przeglądnij zawartość modułu <module>
:module [+/-] [*]<mod> ...  zmień kontekst wykonywania poleceń GHCi (efektywnie         (:m)
                            ładuje funkcje z innego modułu do aktualnej przestrzeni nazw np. 
                            $Prelude>:m Data.List
                            $Prelude Data.List>
:info [<name> ...] 	    wyświetl informacje o danej nazwie                          (:i)
:type <expr>                pokaż typ wyrażenia <expr>			                (:t)  

Proste wyrażenia

W GHCi można wykonywać funkcje Haskella i wyniki są wyświetlane po ich wykonaniu, proszę przetestować:

Precedence Left associative op. Non-associative op. Right associative op.
9!! .
8 ^, ^^, **
7*, /, `div`,
`mod`, `rem`, `quot`
6+, -
5 :, ++
4 ==, /=, <, <=, >, >=,
`elem`, `notElem`
3 &&
2 ||
1>>, >>=

W ramach ćwiczenia proszę pododawać nawiasy do poniższych wyrażeń korzystając z informacji do tej pory zdobytych. Nawiasy należy wstawiać tak, żeby znaczenie wyrażenia nie uległo zmianie i każde podwyrażenie było otoczone nawiasem:

$Prelude> True == False && True || False
$Prelude> max 1 20
$Prelude> succ 1/10
$Prelude> 12 + 4 * 7 `div` 4 ^ 3
$Prelude> elem 2 [1,2,3] : []
$Prelude> (+) 2 3 * 4
$Prelude> length [1,2,3] : 1+7:[]
$Prelude> 2:[]++[1,2,3]
$Prelude> True == False == False

Listy

Listy w Haskellu są podstawowym typem danych złożonych i jednym z najbardziej eksploatowanych przez algorytmy.

Krotki

Listy mogą przechowywać tylko elementy tego samego typu, natomiast elementy różnych typów można grupować w krotki (które mogą być np. elementami listy)

Funkcje

Haskell jest językiem funkcyjnym, czyli zorientowanym na funkcje, ponadto funkcje są pozbawione „efektów ubocznych” i są funkcjami w matematycznym sensie, czyli dla ustalonego parametru funkcja zwaraca zawsze tą samą wartość. Np. w C funkcja wczytająca pojedynczy znak:

 int getchar(void)

nie jest funkcją w matematycznym sensie, kolejne jej wywołania mogą zwrócić inne wartości mimo wciąż tego samego argumentu. Funkcje mogą być obiektami zwracanymi przez inne funkcje. Właściwie w Haskellu istnieją tylko funkcje jednoargumentowe, a możliwość przyjmowania więcej niż jednego argumentu realizuje się przez zwrócenie dla konkretnego argumentu innej funkcji. Np.

$Prelude> (2 *) 3   --aplikuje 2 do funkcji (*), a następnie do zwróconej funkcji aplikowany jest argument 3
$Prelude> (3 *) -- dostajemy komunikat o błędzie, bo GHCi nie wie jak wyświetlić rezultat, czyli funkcję przyjmującą jeden argument
$Prelude> ((2 *).(+ 3)) 1 -- kolejne funkcje można składać (tak jak w matematycznym sensie składanie funkcji) przy pomocy operatora .
$Prelude>  ((take 3).(:[4..9])) 7 -- co robi ta funkcja :?:

Zdefiniujemy teraz własne funkcje. W tym celu proszę utworzyć nowy plik z rozszerzeniem .hs. Proszę w nim utworzyć następującą funkcje:

incAndTriple v = ( v + 1 ) * 3

specialCases 1 = "Hello"
specialCases 2 = " "
specialCases 3 = "World"
specialCases 4 = "!"
specialCases x = "???"

Proszę załadować kod do GHCi, (jak to zrobić:?:) i wykonać np.

$Prelude> incAndTriple 3
$Predlue> map specialCases [1..4]
$Predlue> concat (map specialCases [1..4])
$Predlue> concat (map specialCases [1..6])

map to specjalna funkcja wbudowana, która aplikuje jednoargumentową funkcję podaną jako pierwszy argument do każdego elementu listy podanej jako drugi argument i zwraca listę. Można też korzystać z

$Predlue> map (3*) [1..10]

concat natomiast spłaszcza listę, String w Haskellu to w rzeczywistości synonim listy znaków, dlatego spłaszczenie listy Stringów, daje listę znaków, czyli String.

Debugger

Informacje o poziomie debug można sygnalizować wspomnianą już funkcją Debug.Trace.trace, pomocna też będzie funkcja show, która pokazuje reprezentację wartości w postaci łańcucha znaków.

Poza tym w GHCi jest wbudowany mechanizm do debuggowania kodu.

   :break [<mod>] <l> [<col>]  ustawia breakpoint w wzynaczonej lokalizacji <mod> moduł, <l> linia kodu, <col> kolumna
   :break <name>               lub na funkcji o nazwie <name>
   :step                       wykonanie następnego kroku po zatrzymaniu się na breakpoincie
   :list                       pokaż kod na którym się zatrzyał debugger
   :print [<name> ...]         pokaż wartości zmiennych <name> ...
   :continue                   wznów wykonanie do następnego breakpointu
   :trace [<expr>]             zacznij śledzić wykonanie od tego breakpointu, albo całego wyrażenia <expr>
   :history                    pokaż śledzone wywołania

Jako przykład rozważmy algorytm quicksort (który w Haskellu nie najlepiej się sprawuje ze względu na listy):

import Debug.Trace                                                    -- importuje wszystkie funkcje z modułu Debug.Trace
quicksort [] = []                                                     -- definiuje zachowanie na pustej liście 
quicksort xx@(x:xs)                                                   -- @ inforumuje żeby zmienna xx była widoczna jako całość i jako poszczególne części x i xs
                | trace ("qs: "++ show x ++ " " ++show xx) False = [] -- zastosowano tu składnie | case = execute | case2 = execute2, ponieważ trace zwróci False ta gałąź się nigdy nie wykona, ale zostanie wypisana wiadomość na stderr
                | otherwise = quicksort low ++ x : quicksort high     -- otherwise to nic innego jak True, można nawet sprawdzić True == otherwise w GHCi ta gałąź się wykona w pozostałych przypadkach (w tym wypadku zawsze), następuje konkatenacja listy posortowanej quicksortem z listą składającą się z elementu rodzielającego i listy posortowanej quicksortem
                              where low = [e | e <- xs, e < x]        -- w bloku where następują definicje zmiennych lokalnych low to podlista xs z wartościami mniejszymi od strażnika x
                                    high = [e | e <- xs, e >= x]      -- natomiast high to lista w z wartościami większymi lub równymi strażnikowi 

trace został użyty tylko w celu ukazania leniwego podejścia Haskella i zaprezentowania możliwości śledzenia wartości w kodzie: proszę przetestować powyższy kod dla wejść:

$Prelude> quicksort [3,56,90,23,1,2,5,7,23,89,67]
$Prelude> take 4 (quicksort [1,8,3,5,7,90,34,21,45,9,17])

Proszę też przetestować kombinację poleceń GHCi:

:break Main 5 31
:trace quicksort [1..20]
:list
:step