HASKELL Higher order functions, Modules Janusz Bugajny Piotr Rogulski Łukasz Antoszkiewicz
Higher order functions Funkcje wyższego rzędu
Funkcja wyższego rzędu funkcja, która spełnia przynajmniej jeden z Przyjmuje jedną lub więcej funkcji jako parametr dwóch warunków: Zwraca funkcję
Funkcje wyższego rzędu są podstawowym elementem programowania funkcyjnego! Jeżeli chcemy definiowad tylko wynik działania naszego programu, a nie jego konkretne kroki i stany jak w tradycyjnym programowaniu, szybko przekonamy się, że funkcje wyższego rzędu są niezastąpione to technika bardzo przydatna w rozwiązywaniu problemów.
Curried functions Oficjalnie, każda funkcja w Haskellu przyjmuje tylko jeden parametr. Oczywiście programista może zdefiniowad funkcję z wieloma parametrami, ale wtedy pod spodem tworzy się tak naprawdę kilka funkcji, z których każda przyjmuje po jednym parametrze. Funkcje z więcej niż jednym parametrem to tzw. funkcje rozwijane curried functions. Termin ten pochodzi od nazwiska amerykaoskiego logika Haskella Curry ego (na jego cześd zostały też nazwane języki Haskell i Curry)
Curried functions - przykład Napiszmy prostą funkcję zwracającą iloczyn trzech liczb multthree :: ( Num a) => a -> a -> a -> a multthree x y z = x * y * z Pamiętajmy, że możemy wywoład metodę ze zbyt małą liczbą parametrów (uzyskujemy wtedy efekt podobny przeciążania metod albo ustalenia parametrów domyślnych w C++/Javie/C#) Zapis multthree 3 5 9 jest jednoznaczy z ((multthree 3) 5) 9 Na początku do funkcji jest przekazywana liczba 3. Tworzy to funkcję, która przyjmie jeden parametr i przemnoży go przez 3. Potem przekazywana jest 5. Ponownie tworzona jest funkcja, która przyjmuje jeden parametr i przemnoży go przez 15 (3*5). Do tej drugiej funkcji przekazywana jest 9 i zwrócony jest wynik 135.
Curried functions przykład c.d. Typ naszej funkcji równie dobrze więc mógłby byd zapisany w ten sposób: multthree :: ( Num a) => a -> (a -> (a -> a)) Przed każdym -> występuje typ przyjmowany, a po nim typ zwracany Nasza funkcja zatem przyjmuje jakiś typ liczbowy a i zwraca funkcję typu (Num a) =>a -> (a -> a). Analogicznie, ta funkcja bierze a i zwraca funkcję typu (Num a) =>a -> a. A ta ostatnia funkcja po prostu bierze a i zwraca a.
Curried functions przykład 2 Rozważmy prostą funkcję dzielenia liczby przez 10. dividebyten :: (Floating a) => a -> a dividebyten x = x/10 Okazuje się, że dokładnie ten sam efekt możemy osiągnąd nie podając w ogóle parametru do deklaracji funkcji dividebyten = (/10) Co to za czary?
Curried functions przykład 2 c.d. Otóż zapis (/10) zwraca nam nie liczbę, a funkcję dzielenia przez 10 Inaczej mówiąc, stworzyliśmy sobie nową funkcję dzielenia, w której po prawej stronie dzielnika będzie 10, a drugi argument jest dostarczany przy wowływaniu funkcji dividebyten. Main> dividebyten 35 3.5
Higher-orderism in action Funkcje mogą przyjmowad funkcje jako parametr i zwracad funkcje. Przykład: funkcja która przyjmuje funkcję i aplikuje ją dwukrotnie do czegoś applytwice :: (a -> a) -> a -> a applytwice f x = f (f x) Pierwszy parametr jest funkcją, która przyjmuje coś i zwraca ten sam typ (dlatego jest w nawiasach) Drugi parametr też musi byd tego samego typu, abyśmy mogli zaaplikowad do niego funkcję z pierwszego parametru Typ zwracany to również ten sam typ. *Main> applytwice (+10) 10 30
Higher-orderism c.d. funkcja zipwith Ciekawym przykładem jest standardowa funkcja zipwith zipwith ' :: (a -> b -> c) -> [a] -> [b] -> [c] zipwith ' _ [] _ = [] zipwith ' [] = [] zipwith ' f (x:xs) (y:ys) = f x y : zipwith ' f xs ys Pierwszym parametrem jest funkcja która dostaje dwa cosie i zwraca trzeci typ. Mogą to byd trzy te same typy, ale nie muszą. Jeśli w deklaracji mamy a -> b -> c, to zaakceptowana zostanie też funkcja typu a -> a -> a, ale nie odwrotnie! Drugi parametr to lista typu a, ponieważ funkcja łącząca przyjmuje a jako pierwszy parametr. Trzeci to lista b, ponieważ funkcja łącząca przyjmuje b jako drugi parametr. Rezultatem jest lista c.
zipwith przykłady użycia ghci > zipwith ' (+) [4,2,5,6] [2,6,2,3] [6,8,7,9] ghci > zipwith ' max [6,3,2,1] [7,3,1,5] [7,3,2,5] ghci > zipwith ' (++) ["foo ", " bar ", " baz "] [" fighters ", "hoppers "," aldrin "] [" foo fighters ","bar hoppers "," baz aldrin "] ghci > zipwith ' (*) ( replicate 5 2) [1..] [2,4,6,8,10] ghci > zipwith ' ( zipwith ' (*)) [[1,2,3],[3,5,6],[2,3,4]] [[3,2,2],[3,4,5],[5,4,3]] [[3,4,6],[9,20,30],[10,12,12]]
zipwith c.d. Jak widzimy, pojedyncza funkcja wyższego rzędu może mied bardzo wiele zastosowao. Programowanie funkcyjne używa funkcji wyższego rzędu aby wyabstrahowad od kontekstu typowe, często powtarzające się działania takie jak filtrowanie list, przetwarzanie ich elementów itp.
Map Funkcja map dostaje funkcję oraz listę i aplikuję tę funkcję do każdego elementu listy. map :: (a -> b) -> [a] -> [b] To samo można osiągnąd wyrażeniami lamba jednak przeważnie map jest znacznie czytelniejszy, szczególnie przy mapach map. ghci > map (+3) [1,5,3,1,6] [4,8,6,4,9] ghci > map (++ "!") [" BIFF ", " BANG ", " POW "] [" BIFF!"," BANG!","POW!"] ghci > map ( replicate 3) [3..6] [[3,3,3],[4,4,4],[5,5,5],[6,6,6]] ghci > map ( map (^2)) [[1,2],[3,4,5,6],[7,8]] [[1,4],[9,16,25,36],[49,64]] ghci > map fst [(1,2),(3,5),(6,3),(2,6),(2
Filter Funkcja filter bierze predykat (funkcja zwracająca prawdę lub fałsz) oraz listę i zwraca listę elementów spełniających ten warunek. filter :: (a -> Bool ) -> [a] -> [a] ghci > filter ( >3) [1,5,3,2,1,6,4,3,2,1] [5,6,4] ghci > filter (==3) [1,2,3,4,5] [3] ghci > filter even [1..10] [2,4,6,8,10]
takewhile Dostaje predykat i listę, a następnie zwraca elementy od początku listy dopóki predykat jest prawdziwy. takewhile :: (a -> Bool) -> [a] -> [a] *Main> takewhile (/=' ') "Ala ma kota" "Ala *Main> takewhile (<10) [1,2..100] [1,2,3,4,5,6,7,8,9] *Main> sum ( takewhile (<10000) [1,2..1000000] ) 49995000
Lambdy Lambdy to funkcje anonimowe, których używamy gdy potrzebujemy z danej funkcji skorzystad tylko raz. Z reguły piszemy je z celem podstawienia do funkcji wyższego rzędu Lambdę zapisujemy w następujący sposób: najpierw \, potem parametry, strzałka -> i ciało funkcji, całośd umieszczamy w nawiasach Prelude> let matrix = [ [1,2,3],[4,5,6],[7,8,9,0] ] Prelude> filter (\xs -> length xs > 3) matrix [[7,8,9,0]] Prelude> zipwith (\a b -> (a * 10 + b)) [5,4,3,2,1] [1,2,3,4,5] [51,42,33,24,15]
Folds Folds funkcje podobne do map, z tym, że redukują listę do pojedynczej wartości Funkcja typu fold przyjmuje funkcję binarną, jakąś wartośd startową (zwaną accumulatorem) i listę do zwinięcia. Funkcja binarna przyjmuje wartośd startową oraz pierwszy (lub ostatni) element z listy i zwraca nową wartośd accumulatora Potem funkcja jest wywoływana z accumulatorem i drugim w kolejności (lub przedostatnim) elementem listy itd. aż do przejścia całej listy. Przykład implementacji funkcji sumy z użyciem foldl (left fold)
Folds c.d. Podobnie działają funkcje foldl1 i foldr1 Różnica polega na tym, że nie podajemy w nich wartości początkowej Wartością początkową jest pierwszy (bądź ostatni) element listy Prelude> foldl1 (\x acc -> x - acc) [1,2,3,4,5] -13
Scans Scanl, scanr, scanl1, scanr1 funkcje podobne do foldów, z tym, że zwracają wszystkie kolejne wartości accumulatora w formie listy Prelude> scanl (\x acc -> x + acc) 0 [1,2,3,4,5] [0,1,3,6,10,15]
Kompozycja funkcji W matematyce kompozycja funkcji jest zdefiniowana w następujący sposób: co oznacza wywołanie funkcji f z rezultatem funkcji g. W Haskellu do tego służy operator. (.) :: (b -> c) -> (a -> b) -> a -> c f. g = \x -> f (g x) Zwródmy uwagę na typy. Funkcja f musi przyjmowad ten sam typ, który zwraca funkcja g. Więc funkcja wynikowa przyjmuje ten sam typ co g i zwraca typ zwracany przez f. Prelude> let f = negate. (^3) Prelude> f 20-8000
Moduły w Haskellu Moduł - jest to pewien zbiór powiązaznych ze sobą funkcji i klas. Program zaś jest kolekcją modułów. Dzięki podziałowi na moduły zyskujemy pewne rozgraniczenie funkcjonalności tzn. że poszczególne moduły dostarczają nam specyficzne funkcje przeznaczone do operowania na rożnego rodzaju danych. Moduły z reguły powinny być jaknajbardziej ogólne oraz luźno powiązane dzięki czemu zawarte w nich funkcjonalności można wykorzystac później w wielu programach (wielokrotne wykorzystanie kodu). Z drugiej jednak strony jeśli funkcje z poszczególnych modułów służą wspólnym celom (np. Do Manipulacji listami) są w pewien sposób odgórnie powiązane( ze wzgledu na przeznaczenie) jedna funkcjonalność z jednego modułu, dla konkretnego przypadku potrzebuje innej funkcjonalności z innego modułu. Standardowa biblioteka Haskella jest podzielona na moduły a każdy z nich zawiera funkcje i typy, które sa powiązane z innymi.
Importowanie modułu Aby zaimortować moduł wykorzysujemy komendę import np: import Data. List Takie użycie jest oczywiście rozpoznawane w środowisku GHCi, nie mniej jednak jest ono niezbędne i bardziej charakterystyczne podczas pisana skryptów umieszczonych w plikach, jeśli chcemy odwołać się do konkretnych funkcjonalności pochodzących z określonych modułów. Bardziej charakterystycznym dla środowiska GHCi jest dodawanie modułów za pośrednictwem komendy: :m <nazwa moudułu> np :m + Data. List, co nie zmienia faktu że jedna i druga komenda dodaje funkcje do globalnej przestrzeni nazw, przez to ich działanie staje się jednakowe.
Jeśli chcemy dodać kilka modułów możemy to zrobić poprzez pisanie ich w linij jeden po drugim: :m + Data. List Data. Map Data. Set itd. Jeśli używamy skryptu, który zawiera już wewnątrz zaimportowane moduły nie musimy tego robić z poziomu GHCi Aby opuścić poszczególny moduł używamy komendy: :m
Selekcja funkcjonalności zawartych w module Jeśli interesuje nas konkretna funkcja/funkcje pochodząca(e) z określonego modułu bez potrzeby jego całego ładowania wykorzystujemy konstrukcję typu: import Data. List ( nub, sort ) Jeśli zaś interesują nas wszystkie funkcje z wyjatkiem którejś korzystamy z konstrukcji: import Data. List hiding ( nub )
"Sposób na konflikty" Wyobraźmy sobie sytuację w której dwa moduły, z których korzystamy zawierają funkcje nazwane w ten sam sposób. Podczas jej wywołania Haskell "nie wie", z którego modułu funkcją ma do czynienia. Rozwiązaniem problemu jest zapis: import qualified nazwa_modułu dołączenie modułu nazwa_modulu.nazwa_funkcji użycie To samo w 'wersji skróconej': import qualified nazwa_modulu as nazwa_referencji nazwa_referencji.nazwa_funkcji
Data.List Moduł ten oczywiście (jak sama nazwa wskazuje) dostarcza typ Listowy oraz szereg operacji, których na tym typie bedziemy mogli używać. Intersperse Postępowanie Weź pewien element oraz listę. Umieść wspomniany element pomiędzy każdą parą elementów pochodzących z listy. Przykład: komenda: intersperse '.' "MONKEY" wynik:"m. O.N.K.E.Y " komenda: intersperse 0 [1,2,3,4,5,6] wynik:[1,0,2,0,3,0,4,0,5,0,6]
Data.List Transpose jest pewną odmianą permutacji Postępowanie Niech dana będzie pewna lista list. Weź element z pewnej listy będacej wewnątrz innej( stojący na odpowiedniej pozycji) i zestaw go z elementami, z inych list(będących wewnątrz tej samej listy) stojacymi na tej samej pozycji co on, tak aby powstał nowa lista.ponów działanie dla kolejnych elementów z określonych pozycji. Lepiej może widać to n aprzykładzie :) Przykład komenda: transpose [[1,2,3],[4,5,6],[7,8,9]] wynik: [[1,4,7],[2,5,8],[3,6,9]]
Data.List Intercalate jest pewną odmianą permutacji Postępowanie Weź listę (A) oraz listę list(b). Wstaw listę(a) pomiędzy pary list na liście list(b) utwórz nową listę powstałą na skutek tej operacji. Przykład komenda: intercalate " " [" hey "," there "," guys " ] wynik: " hey there guys "
Data.List Zadanie (transpose) Co zwróci poniższy zapis? transpose ["Op","lis","gjo","aek"]
Data.List concat Postępowanie Połącz elementy z listy list w jedną listę Przykład komenda: concat["za","gad","ka"] wynik: " zagadka"
Data.List??? Zagadka Mając daną listę list : [[1,4,7],[2,5],[3,6,8]] Przekształć ją do pojedyńczej listy postaci: [1,2,3,4,5,6,7,8].Jakich funkcji spośród pozananych należy użyć?
Data.List concatmap Postępowanie Zrób tosamo co Map :) i połącz listę list w jedną. A co robi map? Wykonuje dla wszystkich elementów listy operację podaną jako argument. Przykład komenda: concatmap(replicate 4)[[1,2],[3,4]] wynik: [[1,2],[1,2],[1,2],[1,2],[3,4],[3,4],[3,4],[3,4]] A co zwróciłby map?
Data.List and,or Postępowanie Weź wyrażenie logiczne i zwróc jego wynik=>true lub false. Końcowy wynik jest uzależniony od wartości true lub false dla poszczególnych elementów.tworzona jest tablica wartości logicznych. Przykłady komenda: and $ map ( >4) [5,6,7,8] wynik: True komenda: or $ map (==4) [2,3,4,5,6,1] wynik: True
Data.List Any,all Postępowanie Weź wyrażenie logiczne i sprawdź czy conajmniej jeden(any) lub wszystkie(all) elementy spełniają zapisany warunek. Przykłady komenda: any (==4) [2,3,5,6,1,4] wynik: True komenda: all (==4) [2,3,5,6,1,4] wynik: False
Data.List iterate Postępowanie Ustal wyrażenie i wartość początkową, od której zaczniesz je wykonywać.pamiętaj o ograniczeniu operacji, bo wpadniesz w petlę nieskończoną. Przykłady komenda: take 10 $ iterate (*3) 1 wynik: [1,3,9,27,81,243,729,2187,6561,19683] komenda: iterate (*3) 1 wynik: pętla nieskończona
Data.List Split Postępowanie Weź listę i liczbę, rozdziel listę na tyle elementów na ile wskazuje podana liczba. Przykłady komenda: splitat 3 " heyman " wynik: (" hey "," man " ) komenda: splitat ( -3) " heyman " wynik: (""," heyman " )
Data.List takewhile Postępowanie Wybierz elementy z listy spęłniające warunek. Działanie to wykonaj dla pojedyńczych egzemplaży w przypadku gdy istnieją powtórzenia. Przykłady komenda: takewhile ( >3) [6,5,4, 1] wynik: [6,5,4]
Data.List dropwhile Postępowanie Weź listę i usuń z niej elementy spełniające warunek. Działanie to wykonaj dla pojedyńczych egzemplaży w przypadku gdy istnieją powtórzenia. Przykłady komenda: dropwhile ( <3) [1,2,2,2,3,4,5,4,3,2,1] wynik: [3,4,5,4,3,2,1]
Data.List span Postępowanie Sprawdź warunek w span,dokonaj podziału. Przykłady komenda:span(/=4)[1,2,3,4,5,6,7] wynik: ([1,2,3],[4,5,6,7])
Data.List break Postępowanie Wykonaj z zaprzeczeniem warunek inaczej niż w span. Przykłady komenda:break (==4) [1,2,3,4,5,6,7] wynik: ([1,2,3],[4,5,6,7])
Data.List sort Postępowanie Sortuje listę w kolejności rosnącej Przykłady komenda:sort [8,5,3,2,1,6,4,2] wynik: [1,2,2,3,4,5,6,8]
Data.List group Postępowanie Utwórz grupy jednakowych elementów.z listy wybierz jednakowe elementy i umieść je w osobnych listach,utwórz listę list. Przykłady komenda: group [1,1,1,1,2,2,2,2,3,3,2,2,2,5] wynik: [[1,1,1,1],[2,2,2,2],[3,3],[2,2,2],[5]]
Data.List tails vs tail Postępowanie tails-weź listę, utwórz z niej podciągi ciągów(liste list) usuwając kolejno elementy z początku. tail-tworzy pojedyńczy podzbiór zbioru Przykłady komenda: tails[1,2,3,4] wynik: [[1,2,3,4],[2,3,4],[3,4],[4],[]] komenda: tail[1,2,3,4] wynik: [2,3,4]
Data.List inits Postępowanie Weź listę, utwórz z niej listy poprzez kolejne "odcinanie" jej elementów począwszy od piewrwszego.pierwszym elementem jest lista pusta. Przykłady komenda: inits[1,2,3,4] wynik: [[],[1],[1,2],[1,2,3],[1,2,3,4]] komenda:inits[1,2,3,4] wynik: [2,3,4]
Data.List Inne funkcje: IsPrefixOf IsSufixOf Elem NotElem Partition Find ElemIndex FindIndex FindIndices Zip4 ZipWith3
Data.List ZipWith4 Lines Unlines Words Delete Nub Union Intersect Index...
Data.Char Jest modułem, który dostarcza nam zbiór funkcjonalności, dzięki którym możemy przeprowadzać operacje na pojedyńczych symbolach. Daje nam to szerokie możliwości w kontekście przetwarzania ciągów znakowych(strings),gdyż są one poprostu listami symboli.
Data.Char Zbiór funkcji operujących na pojdyńczych symbolach i sprawdzających czy warunek z mimi związany jest prawdziwy bądź nie. iscontrol sprawdza czy symbol jest znakiem kontrolnym isspace sprawdza czy symbol jest białym znakiem islower sprawdza czy synbol jest napisany z małej litery isupper sprawdza czy synbol jest napisany z wielkiej litery isalpha sprawdza czy synbol jest literą czy cyfrą -cyfra(false) isalphanum sprawdza czy synbol jest literą czy cyfrą -cyfra(true) isprint sprawdza czy symbol ma możliwość wydrukowania(wypisania). isdigit sprawdza czy symbol jest cyfrą. isoctdigit sprawdza czy symbol jest zapisany w systemie
Data.Char ishexdigit sprawdza czy symbol jest zapisany w systemie szesnastkowym. isletter sprawdza czy symbol jest literą ismark sprawdza czy symbol posiada swój odpowiednik w Unicode isnumber sprawdza czy symbol jest cyfrą ispunctuation sprawdza czy symbol jest znakiem interpunkcyjnym issymbol sprawdza czy symbol jest znakiem matematycznym lub znakiem waluty
Data.Char isseparator sprawdza czy symbol jest separatorem bądź białym znakiem isascii sprawdza, czy symbol wpada do pierwszych 128 znaków z zestawu znaków Unicode. islatin1 sprawdza, czy bohater wpada do pierwszych 256 znaków z zestawu znaków Unicode. isasciiupper sprawdza czy symol jest 'wielkim' znakiem ASCII. isasciilower sprawdza czy symol jest 'małym' znakiem ASCII Ogólna sygnatura funkcji przyjmuje postać: Char -> Bool.
Data.Char words Postępowanie Weź ciąg znakowy i podziel go na podciągi w zależności od występowania białego znaku.na tej podstawie utwórz listę (lista string). Przykłady komenda: words "Ala ma kota" wynik:["ala","ma","kota"]
Data.Char Funkcje konwersji inttodigit,digittoint Postępowanie Weź typ wyjściowy i zamień go na inny. Przykłady komenda: map digittoint " 34538 " wynik:[3,4,5,3,8] komenda:inttodigit 5 wynik: '5' ograniczenie:zakres 1..15
Data.Char ord,chr Postępowanie Weź symbol i zamień go na jego cyfrowy odpowiednik (ord) i na odwrót(chr). Przykłady komenda:ord 'a' wynik: 97 komenda:chr 90 wynik: 'Z'
Data.Map Tablica asocjacyjna (tablica skojarzeniowa, mapa, słownik, ang. associative array, map, dictionary) nazwa dla powszechnie stosowanego w informatyce abstrakcyjnego typu danych, który przechowuje pary (unikatowy klucz, wartośd) i umożliwia dostęp do wartości poprzez podanie klucza.
W Haskell mapy dostępne są w module Data.Map, ich implementacja opiera się na drzewach, dlatego znalezienie wartości dla danego klucza odbywa się o wiele szybciej, niż w przypadku list, ponieważ w liście trzeba przechodzid wszystkie elementy, aż do momentu napotkania klucza Jeśli chcemy używad tego modułu musimy go zaimportowad import qualified Data.Map as Map
Tworzenie mapy Map.empty reprezentuje pustą mape let map = Map.empty Metoda fromlist przyjmuje liste i zwraca mape, jeśli w liście są powtórzenia, zostają one pominięte let map = Map.fromList [(1,21), (2,32), (3,5)]
Jeśli chcemy, by powtórzenia w liście nie były tylko pomijane, możemy użyd funkcji Map.fromListWith, która przyjmuje funkcje opisującą co ma zrobid, gdy napotka powtórzenie, oraz liste let mapa = Map.fromListWith (+) [(2,3),(2,5),(4,8)] Map.toList mapa Output: [(2,8), (4,8)] let mapa = Map.fromListWith (\x y -> y) [(1,2),(1,3),(2,4)] Map.toList mapa Output: [(1,3),(2,4)]
Wstawianie wartości do mapy Map.insert przyjmuje klucz, wartośd i mape, i zwraca nową mape utworzoną na podstawie przekazanej mapy, wraz z nowym elementem let mapa = Map.insert klucz wartosc Map.empty let mapa2 = Map.insert klucz2 wartosc2 mapa Map.insertWith analogiczna funkcja do Map.fromListWith
Usuwanie wartości z mapy Delete - przyjmuje klucz i mape, zwraca nową mape bez elementu, który znajdował się pod przekazanym kluczem let mapa = Map.fromList *(1, wartosc ),(2, wartosc )+ let mapa2 = Map.delete 2 mapa Map.toList mapa2 Output: *(1, wartosc )+
Map.member przyjmuje klucz i mape, zwraca wartośd logiczną czy mapa zawiera dany klucz Map.member 3 $ Map.fromList [(3,6),(4,3),(6,9)] Output: True Map.lookup przyjmuje klucz i mape, zwraca wartośd przypisaną do klucza let mymap = Map.fromList[(3,6),(4,2)] Map.lookup 3 mymap Output: Just 6
Map.size przyjmuje mape i zwraca ilośd jej elementów let mymap = Map.fromList [(1,2),(2,3)] Map.size mymap Output: 2 Map.null przyjmuje mape i zwraca wartośd logiczną czy mapa jest nullem Map.null mymap Output: False
Map.map pozwala modyfikowad całą mape w określony sposób let mymap = Map.map (*100) $ Map.fromList[(1,1),(2,4),(3,9)] w tym przypadku wszystkie wartości zostaną pomnożone * 100, mapa będzie wyglądad następująco: [(1,100),(2,400),(3,900)]
Map.keys przyjmuje mape i zwraca wszystkie klucze w niej zawarte Map.keys Map.fromList [(1,21),(2,22),(2,13)] Output:[1,2,3] Map.elems przyjmuje mape i zwraca wszystkie jej wartości
Data.Set Data.Set jest to moduł oferujący nam strukture danych, w której każdy element jest unikalny. Można wykonywad na nim takie operacje m.in. jak, wstawianie, usuwanie, sprawdzanie czy coś należy już do setu oraz konwersje do listy. Jego implementacja oparta jest na drzewach Aby używad tego modułu musimy dokonad importu: import qualified Data.Set as Set
Tworzenie Setu fromlist przyjmuje liste, a zwraca set let myset = Set.fromList przykladowy tekst Po wypisaniu zawartości tego setu, zobaczymy: Output: adekloprstwyz Widzimy, że wartości setu są unikalne i posortowane
Intersection funkcja przyjmuje dwa sety, i zwraca ich częśd wspólną let set1 = Set.fromList abcdefgh let set2 = Set.fromList azcdefgk Set.intersection set1 set Output: acdefg Difference funkcja przyjmuje dwa sety, i zwraca elementy które są w pierwszym secie, ale nie ma ich w drugim Set.difference set1 set2 Output: bh
Union funkcja przyjmuje dwa sety, i zwraca set ze wszystkimi elementami pierwszego i drugiego setu let set1 = Set.fromList abcdefgh let set2 = Set.fromList azcdefgk Set.union set1 set2 Output: abcdefghkz
Moduł Data.Set oferuje nam jeszcze funkcje null, size, member, empty, insert, delete, tolist działające analogicznie jak w module Data.Map Sety są często używane do wyodrębnienia powtórzeo z listy poprzez zamiane listy na set, a później z powrotem na liste. Funkcja nub z Data.List robi tą czynnośd, jednak zamiana na set i z powrotem, działa o wiele szybciej, co przy dużej liście, może byd istotne.
Moduły Moduł - jest to pewien zbiór powiązanych ze sobą funkcji i klas. Program zaś jest kolekcją modułów. Dzięki podziałowi na moduły zyskujemy pewne rozgraniczenie funkcjonalności tzn. że poszczególne moduły dostarczają nam specyficzne funkcje przeznaczone do operowania na rożnego rodzaju danych. Moduły pozwalają nam na to, że raz napisane funkcje, czy definicje typów, łatwo używad w innych programach, bo wystarczy dany moduł zaimportowad.
Tworzenie swojego modułu Moduły zapisujemy w plikach o rozszerzeniu.hs Nazwa modułu powinna byd taka sama jak pliku Moduł powinniśmy przechowywad w folderze o takiej samej nazwie jak nazwa modułu Szkielet modułu: module Nazwa ( -- metody danego modułu ) where --definicje metod
Przykładowy moduł module Geometria ( polekwadratu, obwodkwadratu, poleokregu, obwodokregu ) where polekwadratu :: Float -> Float polekwadratu bok = bok * bok obwodkwadratu :: Float -> Float obwodkwadratu bok = bok * 4 poleokregu :: Float -> Float poleokregu promien = pi * (promien^2) obwodokregu :: Float -> Float obwodokregu promien = 2 * pi * promien
Jeśli chcemy, aby nasz moduł opisywał tylko konkretną figurę, wtedy jeden duży moduł można napisad w postaci podmodułów. module Geometria.Kwadrat ( pole, obwod )where pole :: Float -> Float pole bok = bok * bok obwod :: Float -> Float obwod bok = bok * 4
module Geometria.Okrag ( pole, obwod )where pole :: Float -> Float pole promien = pi * (promien^2) obwod :: Float -> Float obwod promien = 2 * pi * promien Moduły powinniśmy móc użyd w następujący sposób: - ścieżkę pracy programu powinniśmy mied na tym samym poziomie co folder, w którym znajduje się nasz moduł, a następnie poleceniem: import Geometria.Kwadrat, zaimportowad moduł. Gdybyśmy chcieli używad kilka modułów naraz to musimy użyd polecenia import qualified import qualified Geometria.Kwadrat as Kwadrat import qualified Geometria.Okrag as Okrag
Wkorzystane materiały: Learn You a Haskell for Great Good! Miran Lipovaca