Moduł to zbiór powiązanych ze sobą funkcji, typów i klas typu. Język Haskell stanowi zbiór modułów, gdzie moduł główny załącza pozostałe moduły w

Podobne dokumenty
Haskell Moduły Ładowanie

Moduły. Źródło: a) Wczytywanie modułów. interparse rozdziela każdy element listy wcześniej podanym znakiem

HASKELL. Higher order functions, Modules. Janusz Bugajny Piotr Rogulski Łukasz Antoszkiewicz

REKURENCJA W JĘZYKU HASKELL. Autor: Walczak Michał

- nawiasy kwadratowe oznaczają, że to lista

1 Podstawy c++ w pigułce.

utworz tworzącą w pamięci dynamicznej tablicę dwuwymiarową liczb rzeczywistych, a następnie zerującą jej wszystkie elementy,

Definicje wyższego poziomu

Podstawy Programowania C++

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

Podstawy programowania. Podstawy C# Tablice

Składnia funkcji i Rekurencja w języku Haskell

znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

4. Funkcje. Przykłady

SQL (ang. Structured Query Language)

Zasady programowania Dokumentacja

Typy, klasy typów, składnie w funkcji

Opis: Instrukcja warunkowa Składnia: IF [NOT] warunek [AND [NOT] warunek] [OR [NOT] warunek].

Wstęp do Programowania potok funkcyjny

1 Podstawy c++ w pigułce.

Stałe, znaki, łańcuchy znaków, wejście i wyjście sformatowane

Bash - wprowadzenie. Bash - wprowadzenie 1/39

WEJŚCIE/WYJŚCIE HASKELL ŁUKASZ PAWLAK DARIUSZ KRYSIAK

Przykłady zastosowań funkcji tekstowych w arkuszu kalkulacyjnym

Algorytmy i złożoności Wykład 5. Haszowanie (hashowanie, mieszanie)

Nazwa implementacji: Nauka języka Python wyrażenia warunkowe. Autor: Piotr Fiorek. Opis implementacji: Poznanie wyrażeń warunkowych if elif - else.

Algorytmy sortujące i wyszukujące

Podstawy programowania. Wykład: 9. Łańcuchy znaków. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

Dla człowieka naturalnym sposobem liczenia jest korzystanie z systemu dziesiętnego, dla komputera natomiast korzystanie z zapisu dwójkowego

Backend Administratora

Ćwiczenie nr 6. Poprawne deklaracje takich zmiennych tekstowych mogą wyglądać tak:

Systemy operacyjne. Laboratorium 8. Perl find

Wykład 3 Składnia języka C# (cz. 2)

Wykresy i interfejsy użytkownika

Typy danych, cd. Łańcuchy znaków

Języki programowania Haskell

Podstawą w systemie dwójkowym jest liczba 2 a w systemie dziesiętnym liczba 10.

; B = Wykonaj poniższe obliczenia: Mnożenia, transpozycje etc wykonuję programem i przepisuję wyniki. Mam nadzieję, że umiesz mnożyć macierze...

Informatyka 1. Plan dzisiejszych zajęć. zajęcia nr 11. Elektrotechnika, semestr II rok akademicki 2008/2009

Pomorski Czarodziej 2016 Zadania. Kategoria C

Języki programowania wysokiego poziomu. PHP cz.4. Bazy danych

8. Wektory. Przykłady Napisz program, który pobierze od użytkownika 10 liczb, a następnie wypisze je w kolejności odwrotnej niż podana.

Python wprowadzenie. Warszawa, 24 marca PROGRAMOWANIE I SZKOLENIA

Operatory AND, OR, NOT, XOR Opracował: Andrzej Nowak Bibliografia:

Rekurencja (rekursja)

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

C++ Przeładowanie operatorów i wzorce w klasach

INŻYNIERIA BEZPIECZEŃSTWA LABORATORIUM NR 2 ALGORYTM XOR ŁAMANIE ALGORYTMU XOR

Przekształcanie wykresów.

Podstawy i języki programowania

SQL - Structured Query Language -strukturalny język zapytań SQL SQL SQL SQL

Wyrażenie include(sciezka_do_pliku) pozwala na załadowanie (wnętrza) pliku do skryptu php. Plik ten może zawierać wszystko, co może się znaleźć w

Podstawy programowania skrót z wykładów:

Informatyka II. Laboratorium Aplikacja okienkowa

Tablice. Jones Stygar na tropie zmiennych

lekcja 8a Gry komputerowe MasterMind

dr inż. Jarosław Forenc

Po uruchomieniu programu nasza litera zostanie wyświetlona na ekranie

Projekt współfinansowany przez Unię Europejską w ramach Europejskiego Funduszu Społecznego

Serwer WWW Apache. Plik konfiguracyjny httpd.conf Definiujemy m.in.: Aktualne wersje 2.4.6, , zakończony projekt

2. Zmienne i stałe. Przykłady Napisz program, który wypisze na ekran wynik dzielenia 281 i 117 w postaci liczby mieszanej (tj. 2 47/117).

Algorytmy i struktury danych. Wykład 4 Tablice nieporządkowane i uporządkowane

Języki i techniki programowania Ćwiczenia 2

Tabela wewnętrzna - definicja

Funkcje wyszukiwania i adresu PODAJ.POZYCJĘ

Konwersje napis <-> liczba Struktury, unie Scanf / printf Wskaźniki

Niezwykłe tablice Poznane typy danych pozwalają przechowywać pojedyncze liczby. Dzięki tablicom zgromadzimy wiele wartości w jednym miejscu.

Laboratorium Wstawianie skryptu na stroną: 2. Komentarze: 3. Deklaracja zmiennych

Algorytmika i Programowanie VBA 1 - podstawy

Lekcja 10. Uprawnienia. Dołączanie plików przy pomocy funkcji include() Sprawdzanie, czy plik istnieje przy pmocy funkcji file_exists()

System imed24 Instrukcja Moduł Analizy i raporty

Indeksowanie w bazach danych

Jak wykonać inwentaryzację (spis z natury) w rc sklep lub rc sklep mini.

Lekcja : Tablice + pętle

Ile waży arbuz? Copyright Łukasz Sławiński

SQL, LIKE, IN, CASE, EXISTS. Marcin Orchel

Wstęp do informatyki- wykład 2

Algorytmy i struktury danych

Podstawy programowania w C++

0 + 0 = 0, = 1, = 1, = 0.

Opis klawiatury komputerowej

1. Operacje logiczne A B A OR B

Struktury, unie, formatowanie, wskaźniki

ZMIENNE. Podstawy PHP

Słowem wstępu. Część rodziny języków XSL. Standard: W3C XSLT razem XPath 1.0 XSLT Trwają prace nad XSLT 3.0

Informacje ogólne. Karol Trybulec p-programowanie.pl 1. 2 // cialo klasy. class osoba { string imie; string nazwisko; int wiek; int wzrost;

Bazy danych. Bazy danych. Podstawy języka SQL. Dr inż. Paweł Kasprowski.

Algorytmy i struktury danych. Wykład 6 Tablice rozproszone cz. 2

Język skryptowy: Laboratorium 1. Wprowadzenie do języka Python

I. Podstawy języka C powtórka

Inicjacja tablicy jednowymiarowej

WYRAŻENIA ALGEBRAICZNE

KORPORACYJNE SYSTEMY ZARZĄDZANIA INFORMACJĄ

Instrukcje warunkowe i skoku. Spotkanie 2. Wyrażenia i operatory logiczne. Instrukcje warunkowe: if else, switch.

Elementy języka Haskell

Laboratorium 3: Tablice, tablice znaków i funkcje operujące na ciągach znaków. dr inż. Arkadiusz Chrobot dr inż. Grzegorz Łukawski

Sortowanie. Bartman Jacek Algorytmy i struktury

3. Instrukcje warunkowe

Strategia "dziel i zwyciężaj"

Transkrypt:

Haskell Moduły

Moduł to zbiór powiązanych ze sobą funkcji, typów i klas typu. Język Haskell stanowi zbiór modułów, gdzie moduł główny załącza pozostałe moduły w celu użycia zawartych w nich funkcji dla określonego celu. Taki stan rzeczy nie pozbawiony jest zalet. Jeśli moduł jest stosunkowo uniwersalny, to funkcje w nim zawarte można zastosować do wielu różnych programów. Jeśli kod jest podzielony na samodzielne moduły, które nie polegają na sobie zbyt dużo (luźno powiązane), można ponownie wykorzystać je później. To sprawia, że zajmowanie się pisaniem kodu jest bardziej wykonalne poprzez podzielenie go na kilka części, z których każda ma pewnego rodzaju cel.

Ładowanie Modułów Standardowa biblioteka Haskell jest podzielona na moduły, każdy z nich zawiera funkcje i typy, które są w jakiś sposób powiązane i służą pospolitym celom. Znaleźć tu możemy moduł do manipulowania listami, moduł do programowania równoległego, moduł do operacji na liczbach zespolonych, itp.. Wszystkie funkcje, rodzaje i klasy typu, z poprzednich rozdziałów były częścią Prelude module (Modułu Preludyjnego?). Funkcje te są importowane domyślnie.

Składnie dla importowanego modułu w skrypcie Haskell a wygląda następująco import <module name>. Import modułu musi być wykonany przed zdefiniowaniem funkcji, z tego powodu umieszczamy go na szczycie pliku. Dla jednego skryptu można pobrać wiele modułów. Wystarczy umieścić każdy z importowanych komunikatów w osobnym wierszu. Po imporcie modułu, wszystkie zawarte w nim funkcje stają się dostępne w globalnej przestrzeni nazw, co oznacza, że można je wywołać z dowolnego miejsca w skrypcie. Funkcja nub określa w zimportowanym module listę i odsiewa zduplikowane elementy. Kompozycja length i nub wykonana w formie length. nub wytwarza funkcje będącą ekwiwalentem \xs -> lenght (nub xs).

Istnieje również możliwość umieszczenia funkcji z modułów w globalnej przestrzeni nazw poprzez zastosowanie GHCI. Na przykład : ghci > :m + Name. List Jeżeli potrzebujemy wczytać nazwy z kilku modułów wewnątrz GHCI, nie musimy użyć poniższej składni :m + kilkakrotnie Możemy nieco ułatwić sobie życie ghci > :m + Data. List Data. Map Data. Set Jednakże, jeśli masz załadowany skrypt, który już zaimportował moduł, nie trzeba używać M +, aby uzyskać do niego dostęp. Jeżeli potrzebujemy dwóch funkcji z danego modułu to możemy selektywnie importować tylko te funkcje, np. funkcje nub i sort z Data.List import Data. List (nub, sort )

Możemy również wykonać import wszystkich funkcji ze spodziewanego modułu za wyjątkiem kilku z nich. To często przydatne, gdy kilka modułów eksportuje (dostarcza, wypluwa) funkcje o tej samej nazwie i chcesz się pozbyć tych niepotrzebnych. Powiedzmy że już mamy własną funkcje nazwaną nub i chcemy zaimportować wszystkie funkcje z Data.List szczególnie funkcje nub : import Data. List hiding ( nub ) Innym sposobem radzenia sobie z nazwami klas jest wykfalifikowany import (qualified imports). Moduł Data.Map oferuje strukturę danych do przeszukiwania wartości według klucza, eksportuje kilka funkcji o tej samej nazwie jak funkcje Prelude, filtrując lub nie. Więc kiedy importujemy Data.Map i wtedy wywołujemy filtr, Haskell nie wie której funkcji użyć. Oto w jaki sposób rozwiązać ten problem: import qualified Data. Map

To sprawia, że jeśli chcemy odwołać funkcję filtra Data.Map 's, mamy do czynienia z Data.Map.filter, natomiast filter wciąż odnosi się do normalnego filtra. Ale wpisywania Data.Map z przodu każdej funkcji z tego modułu jest trochę uciążliwe. Dlatego możemy zmienić nazwę importu wykfalifikowanego na coś krótszego: import qualified Data. Map as M Teraz aby, odnieść się do funkcji Data.Map s filter, musimy po prostu użyć M.filter. Używamy tych poręcznych odniesień aby sprawdzić które moduły są w bibliotece standardowej. Świetnym sposobem aby pozyskać nową wiedzę z Haskell a jest po prostu klikniecie standardowej biblioteki odniesień (Reference Library) i otrzymanie modułów i ich funkcji. Aby sprawdzić funkcje albo znaleźć ich lokalizacje, używamy Hoogle. Hoogle to Haskell owy silnik przeszukiwania przy pomocy nazwy, nazwy modułu a nawet typu sygnatury.

Data.List Rzućmy okiem na nieużywane do tej pory funkcje. Intersperse bierze element i listę, a następnie umieszcza ten element pomiędzy każdą parą elementów na liście. Oto demonstracja: ghci > intersperse. " MONKEY " "M.O.N.K.E.Y" ghci > intersperse 0 [1,2,3,4,5,6] [1,0,2,0,3,0,4,0,5,0,6] Intercalate pobiera listę list i listy. Następnie umieszcza tę listę między wszystkimi listami, a następnie wyrównuje wynik. ghci > intercalate " " [" hey "," there "," guys "] " hey there guys " ghci > intercalate [0,0,0] [[1,2,3],[4,5,6],[7,8,9]] [1,2,3,0,0,0,4,5,6,0,0,0,7,8,9]

Transpose transponuje listę list. Jeśli spojrzeć na listę list jako na dwuwymiarową (2D) macierz, kolumny stały się wierszami i odwrotnie. ghci > transpose [[1,2,3],[4,5,6],[7,8,9]] [[1,4,7],[2,5,8],[3,6,9]] ghci > transpose [" hey "," there "," guys "] [" htg "," ehu "," yey ","rs","e"] Gdy poddamy transpozycji te trzy listy, trzecie uprawnienia znajdują się w pierwszym wierszu, drugie uprawnienia w drugim i tak dalej. Mapowanie sum do tego produkuje pożądany rezultat.

foldl, foldl1, foldr i foldr1 to ścisłe wersje swoich odpowiednich leniwych wcieleń. Podczas korzystania z leniwych fałd dla bardzo dużych list, możemy często dostać błąd przepełnienia stosu. Sprawcą jest to, że ze względu na leniwy charakter fałd, wartość akumulatora nie jest właściwie aktualizowana gdy fałdowanie ma miejsce. To co wówczas zachodzi w akumulatorze zwiastuje pozytywną obietnice że obliczy wartość gdy skieruje pytanie do aktualnie wyprodukowanego rezultatu. To się dzieje dla każdego pośredniego akumulatora, a wszystkie te obietnice przepełniają stos. Surowe fałdy obliczają pośrednie wyniki zamiast zapełniać stos obietnicami. Więc jeśli kiedykolwiek otrzymasz stos przepełniony błędami robiąc leniwe fałdy, spróbuj przejścia na ich surowych wersjach.

Concat spłaszcza listę list do listy elementów. ghci > concat [" foo "," bar "," car "] " foobarcar " ghci > concat [[3,4,5],[2,3,4],[2,1,1]] [3,4,5,2,3,4,2,1,1] To po prostu usunie jeden poziom zagnieżdżenia. Więc jeśli chcesz całkowicie spłaszczyć [[[2,3], [3,4,5], [2]], [[2,3], [3,4]]], co jest listą list z, list trzeba złączyć je dwukrotnie. Wykonywany concatmap jest taki sam jak pierwsze mapowanie funkcji na liście, a następnie złączenie listy concat (złączem?). ghci > concatmap ( replicate 4) [1..3] [1,1,1,1,2,2,2,2,3,3,3,3]

and przyjmuje listę wartości logicznych i zwraca true tylko wtedy, gdy wszystkie wartości w liście są prawdziwe. ghci > and $ map ( >4) [5,6,7,8] True ghci > and $ map (==4) [4,4,4,3,4] False or jest jak and zwraca true wtedy, gdy choć jedna wartości z listy jest prawdziwa. ghci > or $ map (==4) [2,3,4,5,6,1] True ghci > or $ map ( >4) [1,2,3] False

any i all biorą orzecznik, a następnie sprawdzają, czy którekolwiek lub wszystkie elementy listy spełniają predykat odpowiednio. Zazwyczaj używamy tych dwóch funkcji zamiast mapowania na liście, a następnie robi and lub or. ghci > any (==4) [2,3,5,6,1,4] True ghci > all ( >4) [6,9,10] True ghci > all ( elem [ A.. Z ]) " HEYGUYSwhatsup " False ghci > any ( elem [ A.. Z ]) " HEYGUYSwhatsup " True iterate wykonuje funkcję i wartość początkową. To odnosi funkcje do wartości początkowej, wówczas to dotyczy tego rezultatu funkcji, wówczas to dotyczy wyniku funkcji ponownie, itp. zwraca wszystkie wyniki w postaci nieskończonej listy. ghci > take 10 $ iterate (*2) 1 [1,2,4,8,16,32,64,128,256,512] ghci > take 3 $ iterate (++ " haha ") " haha " [" haha "," hahahaha "," hahahahahaha "]

splitat podejmuje numer oraz listę. Następnie dzieli listę na wiele elementów, wracający wyniki to dwie listy w krotce. ghci > splitat 3 " heyman " (" hey "," man ") ghci > splitat 100 " heyman " (" heyman ","") ghci > splitat ( -3) " heyman " (""," heyman ") ghci > let (a,b) = splitat 3 " foobar " in b ++ a " barfoo takewhile jest to bardzo przydatna funkcja. Pobiera elementy z listy podczas gdy orzecznik trzyma i wtedy kiedy element jest napotkany w taki sposób że nie spełnia predykatu, jest ona przerwana. Okazuje się że jest to bardzo przydatne. ghci > takewhile ( >3) [6,5,4,3,2,1,2,3,4,5,4,3,2,1] [6,5,4] ghci > takewhile (/= ) " This is a sentence " " This "

Powiedzmy że chcemy znać sumę wszystkich trzech wartości, które znajdują się poniżej 10.000. Nie możemy na mapie(^ 3) [1..], zastosować filtr, a następnie spróbować podsumować, ponieważ filtrowanie nieskończonej listy nigdy nie dobiegnie końca. Wszystkie elementy tutaj są rosnąco, ale Haskell tego nie robi. Dlatego możemy to zrobić: ghci > sum $ takewhile ( <10000) $ map (^3) [1..] 53361 Stosujemy (^ 3) do listy nieskończonej a następnie raz na element, który znajduje się ponad 10.000, lista jest przerwana. Teraz możemy podsumować to łatwiej.

dropwhile jest podobny, tylko gdy spadnie/upuszczania wszystkich elementów, podczas gdy orzecznik jest prawdziwy (true) funkcja ta upuszcza wszystkie elementy. Jeden orzecznik równy False, zwraca resztę listy. ghci > dropwhile (/= ) " This is a sentence " " is a sentence " ghci > dropwhile ( <3) [1,2,2,2,3,4,5,4,3,2,1] [3,4,5,4,3,2,1] Możemy podać listę, która reprezentuje wartość zbioru według daty. Lista jest wykonane z krotek, których pierwszym elementem jest zbiór wartość, drugi to rok, trzeci miesiąc, a czwarty jest (date) liczba porządkowa (o ile prawidłowo rozumiem). Chcemy wiedzieć gdy zbiór wartości pierwszy raz przekroczył tysiąc dolarów! ghci > let stock = [(994.4,2008,9,1),(995.2,2008,9,2),(999.2,2008,9,3),(1001.4,2008,9,4),(998.3, 2008,9,5)] ghci > head ( dropwhile (\( val,y,m,d) -> val < 1000) stock ) (1001.4,2008,9,4)

span to trochę jak takewhile tylko zwraca parę list. Pierwsza lista zawiera wszystko z otrzymanej list od takewhile, zawierałaby gdyby była wywoływana z tego samego predykatu i tej samej listy. Druga lista zawiera część listy, której elementy zostały pożucone. ghci > let (fw, rest ) = span (/= ) " This is a sentence " in " First word :" ++ fw ++ ", the rest :" ++ rest " First word : This, the rest : is a sentence Natomiast span obejmuje listę gdy orzecznik jest prawdą, łamie i przerywa go gdy orzeczenie jest pierwszym prawdziwym. Robiąc break p jest równoważne robieniu span (not. p). ghci > break (==4) [1,2,3,4,5,6,7] ([1,2,3],[4,5,6,7]) ghci > span (/=4) [1,2,3,4,5,6,7] ([1,2,3],[4,5,6,7] Podczas korzystania z break, druga lista w wyniku wystartuje z pierwszym elementem, który spełnia predykat.

sort po prostu sortuje listę. Rodzaj elementów na liście ma być częścią klas typu Ord (typeclass), ponieważ jeżeli elementów listy nie można umieścić w jakimś porządku, to lista nie może być sortowana. ghci > sort [8,5,3,2,1,6,4,2] [1,2,2,3,4,5,6,8] ghci > sort " This will be sorted soon " " Tbdeehiillnooorssstw group pobiera listę i grupy sąsiadujących elementów z podlist jeśli są one równe. ghci > group [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7] [[1,1,1,1],[2,2,2,2],[3,3],[2,2,2],[5],[6],[7]] Jeśli posortować listę przed pogrupowaniem jej, możemy dowiedzieć się, ile razy każdy element pojawi się na liście. ghci > map (\ l@(x:xs) -> (x, length l)). group. sort $ [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7] [(1,4),(2,7),(3,2),(5,1),(6,1),(7,1)]

inits i tails są jak (początek i koniec?), tylko one rekurencyjnie wspierają listę, dopóki nic nie zostanie. ghci > inits " w00t " ["","w","w0"," w00 "," w00t "] ghci > tails " w00t " [" w00t ","00t","0t","t",""] ghci > let w = " w00t " in zip ( inits w) ( tails w) [(""," w00t "),("w","00t"),("w0","0t"),(" w00 ","t"),(" w00t ","")] Użyjmy fałd do zaimplementowania przeszukiwania listy dla podlisty. search :: (Eq a) => [a] -> [a] -> Bool search needle haystack = let nlen = length needle in foldl (\ acc x -> if take nlen x == needle then True else acc ) False ( tails haystack )

Najpierw wywołujemy tails z przeszukiwanej list. Potem przeglądamy każdy ogon i sprawdzamy, czy zaczyna się od tego, czego szukamy. Z tym tworzymy funkcje która zachowuje się jak isinfixof. isinfixof wyszukuje podmenu w obrębie listy i zwraca True jeśli podlista której szukamy zawiera się w liście docelowej. ghci > " cat " isinfixof "im a cat burglar " True ghci > " Cat " isinfixof "im a cat burglar False ghci > " cats " isinfixof "im a cat burglar " False

isprefixof i issuffixof przeszukują listę od początku i od tyłu, odpowiednio. ghci > " hey " isprefixof " hey there!" True ghci > " hey " isprefixof "oh hey there!" False ghci > " there!" issuffixof "oh hey there!" True ghci > " there!" issuffixof "oh hey there " False elem i notelem sprawdzają czy element znajduje się lub nie znajduje się w liście. partition pobiera listę i predykat i zwraca parę list. Pierwsza lista w rezultacie zawiera wszystkie elementy, które spełniają predykat, druga zawiera wszystkie te, które tego nie robią. ghci > partition ( elem [ A.. Z ]) " BOBsidneyMORGANeddy " (" BOBMORGAN "," sidneyeddy ") ghci > partition ( >3) [1,3,5,6,3,2,1,0,3,7] ([5,6,7],[1,3,3,2,1,0,3])

Ważnym jest zrozumienie jak to się różni od span i break : ghci > span ( elem [ A.. Z ]) " BOBsidneyMORGANeddy " (" BOB "," sidneymorganeddy ") Podczas gdy span i break zostały raz wykonane, wówczas gdy napotykają pierwszy element który nie spełnia i spełnia predykat, partition (partycja) przechodzi całą listę i dzieli ją według orzecznika. find pobiera listę i predykat i zwraca pierwszy element, który spełnia orzecznik (predykat). Ale zwraca ten element owinięty w Maybe wartości. Będziemy algebraicznie obejmować typy danych dogłębnie w następnym rozdziale ale teraz, ważne jest abyśmy wiedzieli że : Wartości Maybe mogą być zarówno czymś lub niczym. Podobnie jak lista może być zarówno pustą listą lub listą zawierającą jakieś elementy, wartość Maybe może być zarówno bez elementowa lub jednoelementowa. I podobnie jak w rodzaju listy, powiedzmy, całkowitymi są [Int], dla typu Maybe całkowite jest Maybe Int. Tak czy inaczej, weźmy naszą funkcji find (wyszukiwania) dla spin. ghci > find ( >4) [1,2,3,4,5,6] Just 5 ghci > find ( >9) [1,2,3,4,5,6] Nothing ghci > :t find find :: (a -> Bool ) -> [a] -> Maybe a

Zwróćmy uwagę na rodzaj find. Jego wynikiem jest Maybe a. To trochę tak, jakby dla rodzaju [a] wyłącznie rodzaj Maybe mógł zawierać zarówno brak elementów jak i jeden element, natomiast lista może być bez elementowa, jedno elementowa lub wieloelementowa. Pamiętaj, kiedy szukaliśmy po raz pierwszy nasz zbiór przeszedł na 1000 $. Zrobiliśmy head (dropwhile (\ (val, y, m, d) -> Val <1.000) stock). Pamiętaj, że head nie jest naprawdę bezpieczny. Co by się stało, gdyby nasz zbiór nigdy nie poszedł ponad 1000 $? Podany przez nas dropwhile zwróci pustą listę i otrzymamy head z pustej listy spowoduje wystąpienie błędu. Jeśli jednak przepiszemy to jako find (\(val,y,m,d) -> val > 1000) stock będziemy bezpieczniejsi. Jeśli nasze zbiory niepodeszły do $ 1000 (więc jeśli żaden element nie spełnia predykatu), zwróci nam Nothing. Ale była tam ważna odpowiedź w liście, chcielibyśmy uzyskać, powiedzmy, po prostu Just (1001.4,2008,9,4).

elemindex jest trochę jak elem tylko nie zwraca wartość logicznej. Może zwracać indeks elementu którego poszukujemy. Jeśli element ten nie jest na naszej liście, to zwraca Nothing. ghci > :t elemindex elemindex :: (Eq a) => a -> [a] -> Maybe Int ghci > 4 elemindex [1,2,3,4,5,6] Just 3 ghci > 10 elemindex [1,2,3,4,5,6] Nothing elemindices jest jak elemindex, zwraca tylko listę indeksów, w przypadku elementu szukanego, rośnie w górę na naszej liście kilka razy. Ponieważ używamy listę do reprezentowania indeksów, nie potrzebujemy Maybe typu, ponieważ niepowodzenie może być reprezentowane jako pusta lista, która jest synonimem Nothing. ghci > elemindices " Where are the spaces?" [5,9,13]

findindex jest jak find, ale Maybe zwraca indeks pierwszego elementu który spełnia predykat. findindex zwraca indeksy wszystkich elementów orzeczenia, które spełniają w formie listy. ghci > findindex (==4) [5,3,2,1,6,4] Just 5 ghci > findindex (==7) [5,3,2,1,6,4] Nothing ghci > findindices ( elem [ A.. Z ]) " Where Are The Caps?" [0,6,10,14] W poprzednich rozdziałach omawialiśmy zip i zipwith. Zauważyliśmy, że zip łączy razem dwie listy, zarówno w postaci krotki lub binarnej funkcji (co oznacza że taka funkcja, potrzebuje dwóch parametrów). Ale co, jeśli chcemy zip em połączyć razem trzy listy? Lub zip em połączyć trzy listy z funkcją, która przyjmuje trzy parametry? Cóż, do tego mamy zip3, zip4, itd., oraz zipwith3, zipwith4 itp.. Warianty te wzrastają aż do 7. Choć może to wyglądać jak hack, to działa całkiem dobrze, ponieważ nie ma wiele razy, gdy chcesz zip połączyć 8 list razem (podobno). Istnieje również bardzo sprytny sposób na skompresowanie nieskończonej liczby list, ale książka przynajmniej w rozdziale 7 go nie podaje. ghci > zipwith3 (\x y z -> x + y + z) [1,2,3] [4,5,2,2] [2,2,3] [7,9,8] ghci > zip4 [2,3,3] [2,2,2] [5,5,3] [2,2,2] [(2,2,5,2),(3,2,5,2),(3,2,3,2)] Podobnie jak w przypadku normalnego skompresowania, listy, które są dłuższe niż najkrótsza listy, które są spakowane (zipped) są cięte na wymiar.

lines to przydatna funkcja, gdy ma do czynienia z plikami lub wejściem z kądś. To bierze string i zwraca każdą linijkę tego string u w oddzielnej liście. ghci > lines " first line \ nsecond line \ nthird line " [" first line "," second line "," third line "] n jest postacią dla nowej linii unix. Backslashe mają szczególne znaczenie w ciągach (stringach) języka Haskell i jego postaciach, znakach. unlines jest odwrotnością funkcji lines. Bierze listę stringów (łańcuchów) i łączy je ze sobą za pomocą 'n'. ghci > unlines [" first line ", " second line ", " third line "] " first line \ nsecond line \ nthird line \n"

words i unwords służą do podziału wiersza tekstu słowami lub łączenia list słów w tekście. Bardzo przydatne. ghci > words " hey these are the words in this sentence " [" hey "," these "," are "," the "," words ","in"," this "," sentence "] ghci > words " hey these are the words in this \ nsentence " [" hey "," these "," are "," the "," words ","in"," this "," sentence "] ghci > unwords [" hey "," there "," mate "] " hey there mate Już omówiliśmy nub. Bierze on listę i odsiewa zduplikowane elementy, zwracając listę, której każdy element jest unikalny niczym płatek śniegu! ghci > nub [1,2,3,4,3,2,1,2,3,4,3,2,1] [1,2,3,4] ghci > nub " Lots of words and stuff " " Lots fwrdanu "

delete bierze element z listy i usuwa pierwsze wystąpienie tego elementu na liście. ghci > delete h " hey there ghang!" "ey there ghang!" ghci > delete h. delete h $ " hey there ghang!" "ey tere ghang!" ghci > delete h. delete h. delete h $ " hey there ghang!" "ey tere gang! \ jest funkcją różnicy list. Działa w sposób następujący, dla każdego elementu na liście po prawej stronie, usuwa element pasujący w lewym. ghci > [1..10] \\ [2,5,9] [1,3,4,6,7,8,10] ghci > "Im a big baby " \\ " big " "Im a baby Zapis [1..10] \ [2,5,9] zastępuje delete 2. delete 5. delete 9 $ [1..10].

union działa również jak funkcja na zestawach. Zwraca union (związek) dwóch list. Idzie przez każdy element w drugiej liście i dołącza go do pierwszego, jeśli nie jest już dodany. Uważaj jednak, kopie zostały usunięte z drugiej listy! ghci > " hey man " union " man what s up" " hey manwt sup " ghci > [1..7] union [5..10] [1,2,3,4,5,6,7,8,9,10] intersect działa jak ustawione skrzyżowanie. Zwraca jedynie elementy, które znajdują się na obu listach. ghci > [1..7] intersect [5..10] [5,6,7] insert bierze element i listę elementów, które mogą być sortowane i wstawia go do listy. Będzie to wkładane w taki sposób że, wszystkie elementy, po jego prawej stronie jest większa niż lub równe. Jeśli używamy wstawienia insert do posortowanej listy wynikowej, lista będzie przechowywana w formie posortowanej. ghci > insert 4 [1,2,3,5,6,7] [1,2,3,4,5,6,7] ghci > insert g $ [ a.. f ] ++ [ h.. z ] " abcdefghijklmnopqrstuvwxyz " ghci > insert 3 [1,2,4,3,2,1] [1,2,3,4,3,2,1]

length, take, drop, splitat,!! i replicate wszystkie one biorą Int jako jeden ze swych parametrów, choć mogą one być bardziej ogólne i użyteczne, jeśli wezmą dowolny typ, który jest częścią Integral lub typu klasy Num (w zależności od funkcji). Dlatego Data.List ma swoje bardziej ogólne odpowiedniki, o nazwiach genericlength, generictake, genericdrop, genericsplitat, genericindex i genericreplicate. Na przykład, length ma zapis typu length :: [a] -> Int. Jeśli staramy się uzyskać średnią z listy numerów wykonując let xs = [1..6] in sum xs / length xs, otrzymujemy błąd typu, ponieważ nie można używać / z Int. genericlength, z drugiej strony, ma podpis typu genericlength :: (Num a) =>[b] -> a. Ponieważ Num może zachowywać się jak liczba zmiennoprzecinkowa, uzyskanie średniej wykonując let xs = [1..6] in sum xs / genericlength xs działa dobrze.

nub, delete, union, intersect i group funkcje te posiadają swoje bardziej ogólne odpowiedniki zwane nubby, deleteby, unionby, intersectby i groupby. Różnica między nimi jest taka, że pierwszy zestaw funkcji używa == do testowania równości, podczas gdy te z drugiego zestawu z końcówką By, również podejmują funkcje równości, a następnie porównują je za pomocą tej funkcji równości. group jest taka sama jak groupby (==). Załóżmy, że mamy listę, która opisuje wartości funkcji dla każdej sekundy. Chcemy posegmentować go na podlisty na podstawie wartość poniżej zera i kiedy to zrobił. Jeśli nie weźmiemy normalnej grupy, to wówczas weźmiemy po prostu grupę o równych wartościach sąsiadujących ze sobą. Ale to, co chcemy to pogrupowanie ich według tego, czy są one negatywne, czy też nie. To miejsce, gdzie groupby przychodzi! Funkcja równości wspomagana przez funkcje z By powinna przyjmować dwa elementy tego samego typu i zwracać True, jeśli uzna je za równe według standardów funkcji. ghci > let values = [ -4.3, -2.4, -1.2, 0.4, 2.3, 5.9, 10.5, 29.1, 5.3, -2.4, -14.5, 2.9, 2.3] ghci > groupby (\x y -> (x > 0) == (y > 0)) values [[ -4.3, -2.4, -1.2],[0.4,2.3,5.9,10.5,29.1,5.3],[ -2.4, -14.5],[2.9,2.3]]

Widzimy wyraźnie, które odcinki są pozytywne, a które negatywne. Funkcja równości pobiera dwa elementy, a następnie zwraca True tylko wtedy, gdy oba są ujemne (negatywne) lub jeśli oba są pozytywne. Ta funkcja równości można również zapisać jako xy -> (x > 0) && (y > 0) (x <= 0) && (y <= 0), chociaż możliwe, że pierwszy sposób jest bardziej czytelny. Jeszcze wyraźniejszym sposobem pisania funkcji równości dla funkcji z By jest, jeśli by zaimportować funkcję on z Data.Function. on tak jak poniżej: on :: (b -> b -> c) -> (a -> b) -> a -> a -> c f on g = \x y -> f (g x) (g y) Wykonując (==) 'on' (> 0) zwraca funkcję równości, który wygląda jak xy -> (x> 0) == (y> 0). on używany często z funkcjami By ponieważ z tym, możemy to zrobić: ghci > groupby ((==) on (> 0)) values [[ -4.3, -2.4, -1.2],[0.4,2.3,5.9,10.5,29.1,5.3],[ -2.4, -14.5],[2.9,2.3]]

Podobnie sort, insert, maximum i minimum również mają swoje bardziej ogólne odpowiedniki. Funkcje takie jak groupby biorą funkcję, która określa, kiedy dwa elementy są równe. sortby, insertby, maximumby i minimumby biorą określoną funkcję jeżeli jeden element jest większy, mniejszy lub równy drugiemu. Rodzaj zapisu sortby to sortby :: (a -> a -> Ordering) -> [a] -> [a]. Typ Ordering może mieć wartości LT, EQ lub GT. sort jest odpowiednikiem sortby compare ponieważ porównanie zajmuje tylko dwa elementy, których typ jest w typie klasy Ord i zwraca ich związek zamawiania. Listy mogą być porównywane, wówczas listy są porównywane leksykograficznie. Co zrobić, jeśli mamy listę list i chcemy posortować, nie opierając się na innej liście? Rozwiązać tą kwestie może funkcja sortby. ghci > let xs = [[5,4,5,4,4],[1,2,3],[3,5,4,3],[],[2],[2,2]] ghci > sortby ( compare on length ) xs [[],[2],[2,2],[1,2,3],[3,5,4,3],[5,4,5,4,4]]

Data.Char Moduł Data.Char eksportuje funkcje, które zajmują się znakami. Jest to również pomocne podczas filtrowania i mapowania string ów, ponieważ są tylko listami znaków. Data.Char eksportuje kilka orzeczników ponad znakami. Oznacza to, że funkcje, które mają postać i mówią nam, czy niektóre założenia odnośnie tego są prawdziwe czy fałszywe. Oto takie funkcje: iscontrol sprawdza czy znak jest znakiem kontrolnym. isspace sprawdza czy znak należy do białych znaków. Czyli spacje, znaki tabulacji, znaki nowej linii, itp. islower sprawdza czy znak jest małą literą. isupper sprawdza czy znak jest dużą literą.

isalpha sprawdza czy znak jest literą. isalphanum sprawdza czy znak jest literą lub liczbą. isprint sprawdza czy znak jest do druku. Znaki sterujące, na przykład, nie można wydrukować. isdigit sprawdza czy znak jest cyfrą. isoctdigit sprawdza czy znak jest cyfrą ośmiocyfrową(?). ishexdigit sprawdza czy znak jest cyfrą sześciocyfrową(?). isletter sprawdza czy znak jest literą. ismark sprawdza dla Unicode charakter znaków. Są to znaki, które łączą się z poprzednich listów kształt liter z akcentami(? Litery różnych czcionek). isnumber sprawdza czy znak jest numeryczny.

ispunctuation sprawdza czy znak jest interpunkcyjny. issymbol sprawdza czy znak jest fantazyjny, matematyczny lub jest symbolem waluty isseparator sprawdza przestrzenie Unicode i separatory. isascii sprawdza czy postać wpada do pierwszych 128 znaków z zestawu znaków Unicode. islatin1 sprawdza czy postać wpada w pierwszych 256 znaków Unicode. isasciiupper sprawdza czy znak jest ASCII i dużymi literami. isasciilower sprawdza czy znak jest ASCII i małymi literami.

Wszystkie te predykaty mają podpis typu Char -> Bool. Większość czasu będziemy używać tego aby odfiltrować string i lub coś podobnego. Na przykład, powiedzmy, że robimy program, który pobiera nazwę użytkownika i nazwę użytkownika może składać się wyłącznie ze znaków alfanumerycznych. Możemy użyć Data.List funkcji all w połączeniu z Data.Char predykatów celem ustalenia, czy podana nazwa jest w porządku. ghci > all isalphanum " bobby283 " True ghci > all isalphanum " eddy the fish!" False W przypadku, gdy nie pamiętają, all zajmuje predykat (orzecznik) oraz listę i zwraca True tylko wtedy, gdy predykat (orzecznik) odnosi się do każdego elementu na liście. Możemy również użyć isspace aby zasymulować Data.List funkcyjne words. ghci > words " hey guys its me" [" hey "," guys "," its ","me"] ghci > groupby ((==) on isspace ) " hey guys its me" [" hey "," "," guys "," "," its "," ","me"] ghci >

Data.Char również eksportuje typy danych takie jak Ordering. Typ Ordering może mieć wartość LT, EQ lub GT. Jest to rodzaj wyliczenia. Opisano kilka możliwych wyników, które mogą powstać w wyniku porównania dwóch elementów. Rodzaj GeneralCategory jest również wyliczeniem. Przedstawia nam kilku możliwych kategorii, które postać może wpaść. Główną funkcją uzyskania ogólnej kategorii znaku jest generalcategory. To typ generalcategory :: Char -> GeneralCategory. Istnieje około 31 kategorii, więc nie będzie wymienić ich wszystkich tutaj. ghci > generalcategory Space ghci > generalcategory A UppercaseLetter ghci > generalcategory a LowercaseLetter ghci > generalcategory. OtherPunctuation ghci > generalcategory 9 DecimalNumber ghci > map generalcategory " \t\ na9? " [ Space, Control, Control, UppercaseLetter, DecimalNumber, OtherPunctuation, MathSym bol ] Ponieważ typ GeneralCategory jest częścią klasy typu Eq, możemy również testować rzeczy jak generalcategory c == Space.

toupper zamienia znak na wielkie litery. Przestrzenie, cyfry i tym podobne pozostają niezmienione. tolower zamienia znak na małe litery. totitle zamienia znak na przypadek tytułowy. Dla większości znaków, sprawa jest taka sama, jak przy wielkich literach. digittoint zamienia znak na typ Int. Aby odnieść sukces, postać musi być w zakresach '0'.. '9', 'a'.. 'f' lub 'A'.. 'f'. ghci > map digittoint " 34538 " [3,4,5,3,8] ghci > map digittoint " FF85AB " [15,15,8,5,10,11] inttodigit jest to funkcja odwrotna do digittoint. Przyjmuje Int w zakresie 0..15 i konwertuje go do niższego liter. ghci > inttodigit 15 f ghci > inttodigit 5 5 Funkcje ord i chr poddaje konwersji znaki i odpowiadające im numery i odwrotnie: ghci > ord a 97 ghci > chr 97 a ghci > map ord " abcdefgh " [97,98,99,100,101,102,103,104]

Różnica między wartościami ord dwóch znaków są równe, temu jak daleko od siebie, są w tabeli Unicode. Szyfr Cezara to prymitywna metoda kodowania wiadomości poprzez przesunięcie każdego znaku w nich o stałą liczbę pozycji w alfabecie. Możemy łatwo stworzyć coś w rodzaju naszego własnego szyfru Cezara, tylko nie będziemy (zawężać się) ograniczać się do alfabetu. encode :: Int -> String -> String encode shift msg = let ords = map ord msg shifted = map (+ shift ) ords in map chr shifted Najpierw przekonwertujemy string do listy numerów. Następnie dodajemy wartość przesunięcia do każdego numeru przed konwersją listy numerów z powrotem do znaku. Istnieje możliwość aby napisać ciało tej funkcji w formie map (chr. (+ shift). ord) msg. Spróbujmy kodowania kilku wiadomości. ghci > encode 3 " Heeeeey " " Khhhhh " ghci > encode 4 " Heeeeey " " Liiiii }" ghci > encode 1 " abcd " " bcde " ghci > encode 5 " Marry Christmas! Ho ho ho!" " Rfww ~% Hmwnxyrfx &% Mt%mt%mt&"

To jest zakodowane w porządku. Dekodowana wiadomość jest w zasadzie tylko przeniesieniem jej przez liczbę miejsc, został przesunięty w pierwszym miejscu. decode :: Int -> String -> String decode shift msg = encode ( negate shift ) msg ghci > encode 3 "Im a little teapot " "Lp#d# olwwoh # whdsrw " ghci > decode 3 "Lp#d# olwwoh # whdsrw " "Im a little teapot " ghci > decode 5. encode 5 $ " This is a sentence " " This is a sentence "

Data.Map Listy Association (zwane również słownikami, lub listą stowarzyszenia ) są to listy, które są używane do przechowywania par wartości klucza, gdzie kolejność nie ma znaczenia. Na przykład, możemy użyć listy asocjacji do numerów magazynów, gdzie numery telefonów byłyby wartościami i nazwiska ludzi byłoby kluczami. Nie dbamy o to, w jakiej kolejności są one przechowywane, chcemy tylko, aby uzyskać odpowiedni numer telefonu do właściwej osoby. Najbardziej oczywistym sposobem reprezentowania list stowarzyszonych w Haskell u byłoby poprzez użycie listy par. Pierwszy człon pary będzie kluczem, drugi składnik wartością. Oto przykład z listy stowarzyszeniowej z numerami telefonów: phonebook = [(" betty "," 555-2938 "),(" bonnie "," 452-2928 "),(" patsy "," 493-2928 "),(" lucille "," 205-2928 "),(" wendy "," 939-8282 "),(" penny "," 853-2492 ") ]

Pomimo tych pozornych wcięć, to tylko lista par łańcuchów (string ów). Najczęstszym zadaniem, gdy mamy do czynienia z listą stowarzyszenia jest szukanie wartość jakiegoś klucza. Zróbmy funkcję, która szuka wartości dając klucz. findkey :: (Eq k) => k -> [(k,v)] -> v findkey key xs = snd. head. filter (\(k,v) -> key == k) $ xs Dość proste. Funkcja, która pobiera klucz i listy, filtruje listę tak, że tylko pasujące klucze pozostają, funkcja dostaje pierwszą wartość klucza, która odpowiada i zwraca wartość. Ale co się stanie, jeżeli szukany klucz nie jest na liście stowarzyszenia?. Jeśli klucz nie znajduje się na liście stowarzyszenia, skończymy próbując dostać głowę pustej listy, która generuje błąd wykonania. Jednakże powinniśmy unikać czynienia naszych programów tak łatwo zawodzących, więc użyjmy typ danych Maybe. Jeśli nie znajdzie klucza, to zwróci Nothing. Jeśli go znajdziemy, coś nam rzuci, a tym czyms będzie wartość odpowiadająca temu kluczowi. findkey :: (Eq k) => k -> [(k,v)] -> Maybe v findkey key [] = Nothing findkey key ((k,v): xs) = if key == k then Just v else findkey key xs

Spójrz na deklaracje typu. Wymaga klucza, który można postawić na równi, z lista skojarzeń/stowarzyszeń, a potem może wytworzyć wartość. Jest to rekurencyjny podręcznik funkcji, operującej na liście. Przypadek krawędziowy, dzieli listę na głowe i ogon, wywołania rekurencyjne, wszystkie one się tam znajdują. Jest to klasyczny wzór (fałdu?), więc zobaczymy, jak miałoby to być realizowane jako fałd (krotnie?). findkey :: (Eq k) => k -> [(k,v)] -> Maybe v findkey key = foldr (\(k,v) acc -> if key == k then Just v else acc ) Nothing Zazwyczaj lepiej jest użyć fałd do listy standardowego wzoru rekursji zamiast posłużyć się rekursją, ponieważ są one łatwiejsze do odczytania i identyfikacji. Fałdy kiedy wywołują foldr (widzą połączenia folderu), ale to wymaga wyraźniejszej rekursji(?). ghci > findkey " penny " phonebook Just " 853-2492 " ghci > findkey " betty " phonebook Just " 555-2938 " ghci > findkey " wilma " phonebook Nothing

Jeśli mamy numer telefonu, po prostu uzyskujemy numer, w przeciwnym razie otrzymujemy Nothing. Właśnie wdrożyliśmy funkcje lookup (wyszukiwania) z Data.List. Jeśli chcemy znaleźć odpowiednią wartość do klucza, musimy przeszukać wszystkie elementy z listy, aż ją znajdzie. Moduł Data.Map oferuje listy stowarzyszenia, które są znacznie szybsze (ponieważ są one wewnętrznie realizowane z drzew), a także zapewnia wiele funkcji użytkowych. Od teraz będziemy pracować z mapami zamiast z listami stowarzyszenia. Ponieważ Data.Map eksportuje funkcje, które kolidują z Prelude i Data.List, zrobimy wykwalifikowany import. import qualified Data. Map as Map

Funkcja fromlist pobiera listę skojarzeń (Association list, lista stowarzyszenia or słownik) w postaci listy i zwraca mapę z tych samych skojarzeń. ghci > Map. fromlist [(" betty "," 555-2938 "),(" bonnie "," 452-2928 "),(" lucille "," 205-2928 ")] fromlist [(" betty "," 555-2938 "),(" bonnie "," 452-2928 "),(" lucille "," 205-2928 ")] ghci > Map. fromlist [(1,2),(3,4),(3,2),(5,5)] fromlist [(1,2),(3,2),(5,5)] Jeśli istnieją duplikaty kluczy na pierwotnej liście skojarzeń, duplikaty są po prostu usuwane. To jest podpis typu fromlist. Map. fromlist :: ( Ord k) => [(k, v)] -> Map. Map k v Bierze listę par typu k i v oraz zwraca mapę, która mapuje z kluczy typu k do typu v. Zauważmy, że gdy robiliśmy listy skojarzeń ze zwykłymi listami, tylko klucze musiały być właściwie utożsamiane (ich rodzaj należący do klasy typu Eq), ale teraz mają być zamawiane. To istotne ograniczenie w module Data.Map. Pojawia się konieczność aby zamawiać klucze więc może je zorganizować w drzewie. Zawsze należy używac Data.Map lub klucz-wartość stowarzyszeń, chyba że masz klucze, które nie są częścią klasy Ord.

empty reprezentuje pustą mapę. Nie pobiera żadnych argumentów, on po prostu zwraca pustą mapę. ghci > Map. empty fromlist [] insert bierze klucz, wartość oraz mapy i zwraca nową mapę, która jest taka sama jak stara, tylko z włożonymi do niej kluczami i wartościami. ghci > Map. empty fromlist [] ghci > Map. insert 3 100 Map. empty fromlist [(3,100)] ghci > Map. insert 5 600 ( Map. insert 4 200 ( Map. insert 3 100 Map. empty )) fromlist [(3,100),(4,200),(5,600)] ghci > Map. insert 5 600. Map. insert 4 200. Map. insert 3 100 $ Map. empty fromlist [(3,100),(4,200),(5,600)] Możemy realizować nasz fromlist za pomocą pustej mapy, poprzez wkładanie i fałdy (insert and a fold). fromlist :: ( Ord k) => [(k,v)] -> Map. Map k v fromlist = foldr (\(k,v) acc -> Map. insert k v acc ) Map. empty

null sprawdza czy mapa jest pusta. ghci > Map. null Map. empty True ghci > Map. null $ Map. fromlist [(2,3),(5,5)] False size informuje o rozmiarze mapy. ghci > Map. size Map. empty 0 ghci > Map. size $ Map. fromlist [(2,4),(3,3),(4,2),(5,4),(6,4)] 5 singleton bierze klucz i wartość i tworzy mapę, która ma dokładnie jedno odwzorowanie. ghci > Map. singleton 3 9 fromlist [(3,9)] ghci > Map. insert 5 9 $ Map. singleton 3 9 fromlist [(3,9),(5,9)] lookup działa jak Data.List lookup, działa tylko na mapach. Zwraca Just something jeżeli znajdzie coś dla klucza i Nothing, jeśli tak nie jest.

member jest predykatem bierze klucz, mapę i raporty, nieważne czy klucz jest na mapie, czy też nie. ghci > Map. member 3 $ Map. fromlist [(3,6),(4,3),(6,9)] True ghci > Map. member 3 $ Map. fromlist [(2,5),(4,5)] False map i filter działają podobnie jak ich lista odpowiedników. ghci > Map. map (*100) $ Map. fromlist [(1,1),(2,4),(3,9)] fromlist [(1,100),(2,400),(3,900)] ghci > Map. filter isupper $ Map. fromlist [(1, a ),(2, A ),(3, b ),(4, B )] fromlist [(2, A ),(4, B )] tolist jest odwrotnością fromlist. ghci > Map. tolist. Map. insert 9 2 $ Map. singleton 4 3 [(4,3),(9,2)] keys i elems zwracają listy kluczy i odpowiednie wartości. keys jest równoznaczny do map fst. Map.toList i elems jest równoznaczny do map snd. Map.toList.

fromlistwith działa jak fromlist, tylko nie wyrzuca zduplikowanych kluczów ale używa funkcji dołączonej do niego, aby zdecydować, co z nimi zrobić. Powiedzmy, że dana osoba może mieć kilka numerów i mamy listę stowarzyszenia utworzoną w ten sposób. phonebook = [(" betty "," 555-2938 "),(" betty "," 342-2492 "),(" bonnie "," 452-2928 "),(" patsy "," 493-2928 "),(" patsy "," 943-2929 "),(" patsy "," 827-9162 "),(" lucille "," 205-2928 "),(" wendy "," 939-8282 "),(" penny "," 853-2492 "),(" penny "," 555-2111 ") ] Jeśli po prostu skorzystamy z fromlist, aby umieścić to na mapie, stracimy kilka numerów! Dlatego: phonebooktomap :: ( Ord k) => [(k, String )] -> Map. Map k String phonebooktomap xs = Map. fromlistwith (\ number1 number2 -> number1 ++ ", " ++ number2 ) xs

insertwith wstawia parę wartości kluczy do mapy, ale jeśli mapa zawiera już klucz, wykorzystuje funkcję przekazaną do niej, aby ustalić, co zrobić. ghci > Map. insertwith (+) 3 100 $ Map. fromlist [(3,4),(5,103),(6,339)] fromlist [(3,104),(5,103),(6,339)]

Data.Set Moduł Data.Set oferuje nam dobre, zestawy. Podobnie jak zestawy z matematyki. Zestawy są trochę jak skrzyżowanie list i map. Wszystkie elementy zestawu są unikatowe. A ponieważ są one wewnętrznie realizowane z drzewa (podobnie jak mapy w Data.Map), są one uporządkowane. Sprawdzanie członkostwa, wstawianie, usuwanie, itp jest znacznie szybciej niż robi to samo z list. Najczęstszymi operacjami gdy mamy do czynienia z zestawami to wstawianie do zestawu, sprawdzanie członkostwa i konwersja zestawu do listy. Ponieważ nazwy w Data.Set kolidują z wieloma nazwami Data.List i Prelude, dlatego robimy wykwalifikowany import.

Umieszczając podaną niżej instrukcję import w skrypcie: import qualified Data. Set as Set A następnie załadowójąc skrypt poprzez GHCI. Załóżmy, że mamy dwa fragmenty tekstu. Chcemy dowiedzieć się, jakie znaki zostały użyte w obu z nich. text1 = "I just had an anime dream. Anime... Reality... Are they so different?" text2 = " The old man left his garbage can out and now his trash is all over my lawn! Funkcja fromlist działa tak jak można by oczekiwać. Bierze listy i konwertuje je do zestawu. ghci > let set1 = Set. fromlist text1 ghci > let set2 = Set. fromlist text2 ghci > set1 fromlist ".? AIRadefhijlmnorstuy " ghci > set2 fromlist "! Tabcdefghilmnorstuvwy "

Jak widać, elementy są sortowane, a każdy element jest wyjątkowy. Teraz użyjmy funkcji intersection (przecięcia), aby zobaczyć, które elementy dzielą wspólnie. ghci > Set. intersection set1 set2 fromlist " adefhilmnorstuy Możemy użyć funkcji difference (różnicy), aby zobaczyć, które litery są w pierwszym zestawie (secie), ale nie są w drugim i na odwrót. ghci > Set. difference set1 set2 fromlist ".? AIRj " ghci > Set. difference set2 set1 fromlist "! Tbcgvw Albo możemy zobaczyć wszystkie unikalne liter użyte w obu sentencjach za pomocą użycia union. ghci > Set. union set1 set2 fromlist "!.? AIRTabcdefghijlmnorstuvwy "

Funkcje null, size, member, empty, singleton, insert i delete działają tak jak się tego od nich wymaga. ghci > Set. null Set. empty True ghci > Set. null $ Set. fromlist [3,4,5,5,4,3] False ghci > Set. size $ Set. fromlist [3,4,5,3,4,5] 3 ghci > Set. singleton 9 fromlist [9] ghci > Set. insert 4 $ Set. fromlist [9,3,8,1] fromlist [1,3,4,8,9] ghci > Set. insert 8 $ Set. fromlist [5..10] fromlist [5,6,7,8,9,10] ghci > Set. delete 4 $ Set. fromlist [3,4,5,4,3,4,5] fromlist [3,5]

Możemy również sprawdzić podzbiory lub właściwy podzbiór. Zbiór A jest podzbiorem zbioru B, o ile zawiera on wszystkie elementy, które zawiera A. Zbiór A jest podzbiorem zbioru B, jeśli B zawiera wszystkie elementy, które A posiada, ale A ma jeszcze inne elementy. ghci > Set. fromlist [2,3,4] Set. issubsetof Set. fromlist [1,2,3,4,5] True ghci > Set. fromlist [1,2,3,4,5] Set. issubsetof Set. fromlist [1,2,3,4,5] True ghci > Set. fromlist [1,2,3,4,5] Set. ispropersubsetof Set. fromlist [1,2,3,4,5] False ghci > Set. fromlist [2,3,4,8] Set. issubsetof Set. fromlist [1,2,3,4,5] False

Możemy również mapować map zestawy i filtrować je filter. ghci > Set. filter odd $ Set. fromlist [3,4,5,6,7,2,3,4] fromlist [3,5,7] ghci > Set. map (+1) $ Set. fromlist [3,4,5,6,7,2,3,4] fromlist [3,4,5,6,7,8] Zestawy są często wykorzystywane do wyeliminowania list duplikatów z listy, najpierw czynią to w zestawie za pomocą fromlist, a następnie przekształcają go z powrotem do listy z tolist. Funkcja nub z Data.List już to robi, ale wyeliminowanie duplikatów dla dużych list jest o wiele szybsze, jeśli dopchać je do zestawu, a następnie konwertować je do listy, niż stosując nub. Ale używając nub wymagany jest jedynie rodzaj elementów z listy, aby być częścią klasy typu Eq, natomiast jeżeli chcesz dopchać elementy do zestawu, typ listy musi być w Ord. ghci > let setnub xs = Set. tolist $ Set. fromlist xs ghci > setnub " HEY WHATS CRACKALACKIN " " ACEHIKLNRSTWY " ghci > nub " HEY WHATS CRACKALACKIN " " HEY WATSCRKLIN setnub jest na ogół szybszy niż nub na dużych listach, ale jak widać, nub zachowuje kolejność elementów na liście, podczas gdy setnub nie robi tego.

Making our own modules Zbadaliśmy kilka fajnych modułów do tej pory, ale w jaki sposób stworzyć swój własny moduł? Prawie każdy język programowania pozwala rozdzielić swój kod na kilka plików i z Haskell em nie jest inaczej. Tworząc programy, dobrą praktyką jest, aby podawać funkcje i rodzaje, które działają w kierunku osiągnięcia podobnego celu i umieścić je w module. W ten sposób można łatwo ponownie używać tych funkcji w innych programach po prostu importując moduł. Tworzyenie naszych własnych modułów rozpoczyna się od stworzenia małego moduł, który zawiera pewne funkcje do obliczania objętości i powierzchni kilku obiektów geometrycznych. Zaczniemy od utworzenia pliku o nazwie Geometry.hs.

Moduł eksportuje funkcje. Co to znaczy, że kiedy zaimportuje moduł, to mogę korzystać z funkcji, które zawiera (eksportuje). Można zdefiniować funkcje w taki sposób że funkcje wywoływane są wewnątrz, ale możemy tylko zobaczyć i korzystać z tych, za eksportowanych. Na początku modułu, podajemy nazwę modułu. Jeśli mamy plik o nazwie Geometry.hs, to powinniśmy wymienić nasz moduł Geometry. Następnie określamy funkcje jako eksportująca i po tym, możemy rozpocząć pisanie funkcji. Więc zaczniemy z tym. module Geometry ( spherevolume, spherearea, cubevolume, cubearea, cuboidarea, cuboidvolume ) where

Tworzymy powierzchnie i objętości dla sfer, sześcianów i prostopadłościanów. Następnie definiujemy naszą funkcję, wówczas: module Geometry ( spherevolume, spherearea, cubevolume, cubearea, cuboidarea, cuboidvolume ) where

spherevolume :: Float -> Float spherevolume radius = (4.0 / 3.0) * pi * ( radius ^ 3) spherearea :: Float -> Float spherearea radius = 4 * pi * ( radius ^ 2) cubevolume :: Float -> Float cubevolume side = cuboidvolume side side side cubearea :: Float -> Float cubearea side = cuboidarea side side side cuboidvolume :: Float -> Float -> Float -> Float cuboidvolume a b c = rectanglearea a b * c cuboidarea :: Float -> Float -> Float -> Float cuboidarea a b c = rectanglearea a b * 2 + rectanglearea a c * 2 + rectanglearea c b * 2 rectanglearea :: Float -> Float -> Float rectanglearea a b = a * b

Jest to dość standardowa geometria. Jest kilka rzeczy na które dobrze zwrócić uwagę. Ponieważ sześcian jest tylko szczególnym przypadkiem prostopadłościanu, określiliśmy jego powierzchnie i objętość, traktując go jako prostopadłościan, którego boki są tej samej długości. Definiujemy również funkcję pomocniczą o nazwie rectanglearea, która oblicza obszar prostokąta na podstawie długości jego boków. Tworząc moduł, zwykle eksportujemy tylko te funkcje, które działają jako rodzaj interfejsu do naszego modułu, tak aby realizacja była ukryta. Jeśli ktoś korzysta z naszego modułu Geometry, to nie musi on zajmować się funkcjami, których nie eksportujemy.

Możemy całkowicie zmienić lub usunąć funkcje w nowszej wersji (możemy usuwać rectanglearea i zamiast tego po prostu użyć * ) i nikomu to nie bedzie przeszkadzać, bo nie eksportowaliśmy ich w pierwszej kolejności. Aby użyć modułu, wystarczy zrobić: import Geometry Geometry.hs musi być w tym samym folderze, co program importowany. Moduły mogą być również podane w hierarchicznej strukturze. Każdy moduł może mieć szereg podmodułów a one z kolei mogą mieć własne podmoduły. Przechodzimy przez sekcje tych funkcji po to aby Geometry stała się modułem mającym trzy podmoduły, po jednym dla każdego typu obiektu.

Najpierw tworzymy folder o nazwie Geometry. W nim będziemy umieszczać trzy pliki: sphere.hs, cuboid.hs, i cube.hs. Oto co pliki będą zawierać: sphere.hs module Geometry. Sphere ( volume, area ) where volume :: Float -> Float volume radius = (4.0 / 3.0) * pi * ( radius ^ 3) area :: Float -> Float area radius = 4 * pi * ( radius ^ 2)

cuboid.hs module Geometry. Cuboid ( volume, area ) where volume :: Float -> Float -> Float -> Float volume a b c = rectanglearea a b * c area :: Float -> Float -> Float -> Float area a b c = rectanglearea a b * 2 + rectanglearea a c * 2 + rectanglearea c b * 2 rectanglearea :: Float -> Float -> Float rectanglearea a b = a * b

cube.hs module Geometry. Cube ( volume, area ) where import qualified Geometry. Cuboid as Cuboid volume :: Float -> Float volume side = Cuboid. volume side side side area :: Float -> Float area side = Cuboid. area side side side

Dla wszystkich trzech podmodułów, definiujemy funkcje o tych samych nazwach. Możemy to zrobić, ponieważ są one oddzielnymi modułami. Aby muc użyć funkcji z Geometry.Cuboid w Geometry.Cube nie możemy po prostu zaimportować Geometry.Cuboid ponieważ to by za eksportowało funkcje z taka samą nazwą jak Geometry.Cube. Dlatego stosujemy kwalifikowany import. Więc teraz, gdy jesteśmy w pliku, który znajduje się na tym samym poziomie co folder Geometry, to możemy : import Geometry. Sphere Możemy wówczas wywołać area i volume dadzą nam one powierzchnię i objętość dla kuli. A jeśli chcemy pogodzić dwa lub więcej z tych modułów, to musimy zrobić wykwalifikowanych import, ponieważ one (area i volume ) eksportują funkcje o tych samych nazwach. Dlatego:

import qualified Geometry. Sphere as Sphere import qualified Geometry. Cuboid as Cuboid import qualified Geometry. Cube as Cube A potem możemy wywołac Sphere.area, Sphere.volume, Cuboid.area, itd, a każdy będzie obliczać powierzchnie lub objętości dla ich odpowiedniego obiektu. Dziękuje za uwagę i cierpliwość.