Tematyka:
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)
W GHCi można wykonywać funkcje Haskella i wyniki są wyświetlane po ich wykonaniu, proszę przetestować:
$Prelude> 13 + 90 $Prelude> 9 - 17 $Prelude> 6 * 5 $Prelude> 9 / 2
$Prelude> True && False $Prelude> True || False $Prelude> not False
$Prelude> 2 == 3 $Prelude> 4.5 /= 6.7 $Prelude> "abc" == "abc"
$Prelude> min 2 3 $Prelude> succ 8 $Prelude> max 9 7
$Prelude> 1 $Prelude> True $Prelude> 7.2
$Prelude> succ 3 + max 8 0 * 2 $Prelude> succ 1/10 $Prelude> succ 1/10 == succ 0.1
$Prelude> 2 `max` 3 $Prelude> (+) 2 3
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 w Haskellu są podstawowym typem danych złożonych i jednym z najbardziej eksploatowanych przez algorytmy.
$Prelude> [1,2,3,89] $Prelude> [1..100] $Prelude> ['a'..'z'] $Prelude> [1,6..100] $Prelude> ['a','f'..'z'] $Prelude> [1.0,1.1..5.0] -- uwaga na precyzję, raczej nie używać zmiennoprzecinkowych liczb, można to zrobić lepiej
$Prelude> 1:[] -- wstawianie na początek listy $Prelude> 1:[1,23,4] -- wstawianie na początek listy $Prelude> head [1,4,5] -- pierwszy element listy $Prelude> tail [1,4,5] -- wszystko poza pierwszym $Prelude> last [1,4,5] -- ostatni element $Prelude> init [1,4,5] -- wszystko poza ostatnim $Prelude> length [1,4,5] -- długość listy $Prelude> take 2 [1,4,5] -- wybierz dwa elementy z listy $Prelude> elem 4 [1,4,5] -- czy 4 należy do listy $Prelude> 4 `elem` [1,4,5] -- to samo tylko w postaci infixowej $Prelude> [1,7,0] ++ [1,4,5] -- złączenie list
Trzeba pamiętać jednak, że implementacja opiera się o listę jednokierunkową, dlatego operacja length ma złożoność obliczeniową O(n) i złączenie list ++ musi przeglądnąć całą listę po lewej stronie, żeby znaleźć koniec listy. Najwydajniejsze algorytmy to te, które działają od lewej do prawej na liście.
$Prelude> head [1..] -- pobierz pierwszy element z nieskończonej(!) listy $Prelude> take 20 ([1,2..78]++[(Debug.Trace.trace "Hej ja" 100)])
pobierz 20 pierwszych elementów z listy, utworzenie drugiej listy nie będzie potrzebne, bo pierwsza lista ma więcej elementów, nie zostanie wypisany komunikat, bo kod się nigdy nie wykona, funkcja trace z modułu Debug.Trace wypisuje łańcuch znaków podany jako pierwszy argument i zwraca drugi argument
$Prelude> take 79 ([1,2..78]++[(Debug.Trace.trace "Hej ja" 100)]) -- tym razem pierwsza lista nie wystarczyła
$Prelude> [2*x+1| x <- [0..10]] --tworzy listę elementów postaci 2*x+1, gdzie x są kolejnymi wartościami z listy [0..10] $Prelude> [x| x <- [0..21],x `mod` 2 == 1] -- tworzy listę elementów postaci x, gdzie x są kolejnymi wartościami z listy [0..21] i dodatkowo spełniony jest warunek, że x jest nieparzysty $Prelude> [x| x <- [0..21],odd x] -- jak wyżej tylko z gotową funkcją z Prelude $Prelude> [if odd x then 'a' else 'b'| x <- [1..5]] $Prelude> [x+y*z| x <- [1..5], y<-[7..10], z<-[14,17],2*x/=y]
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)
$Prelude> (2,"String") $Prelude> (1,'a',9.1,87)
$Prelude> fst (2,"String") $Prelude> snd (2,"String") $Prelude> zip ["Maciek","Damian","Wojtek","Anna"] [203009,203017,203002,203081] $Prelude> zip [1..] ["Intelx86","Intel64","ARM","Sparc32"] -- listy nie muszą być równej długości $Prelude> fst (unzip [(1,"a"),(2,"b"),(3,"c")])
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.
getSecondElement (_:x:_) = x
snd (_,x) = x
trd (_,_,x) = x
getJust (Just x) = x
(+++) x y = x+y
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