Haskell - Szybki przewodnik
Haskell to funkcjonalny język programowania, który został specjalnie zaprojektowany do obsługi obliczeń symbolicznych i aplikacji do przetwarzania list. Programowanie funkcjonalne opiera się na funkcjach matematycznych. Oprócz języka Haskell, niektóre z innych popularnych języków, które stosują się do paradygmatu programowania funkcjonalnego, to: Lisp, Python, Erlang, Racket, F #, Clojure itp.
W conventional programing, instrukcje są traktowane jako zestaw deklaracji w określonej składni lub formacie, ale w przypadku functional programingwszystkie obliczenia są traktowane jako połączenie oddzielnych funkcji matematycznych.
Funkcjonalność z Haskellem
Haskell jest szeroko używanym językiem czysto funkcjonalnym. Tutaj wymieniliśmy kilka punktów, które sprawiają, że ten język jest tak wyjątkowy w porównaniu z innymi konwencjonalnymi językami programowania, takimi jak Java, C, C ++, PHP itp.
Functional Language- W konwencjonalnym języku programowania instruujemy kompilator, który wykonuje serię zadań, które są niczym innym, jak mówieniem komputerowi „co ma robić” i „jak to zrobić?” Ale w Haskell powiemy naszemu komputerowi "co to jest?"
Laziness- Haskell to leniwy język. Przezlazy, mamy na myśli to, że Haskell nie oceni żadnego wyrażenia bez powodu. Gdy silnik oceny stwierdzi, że wyrażenie wymaga oceny, tworzy plikthunk data structure zebrać wszystkie wymagane informacje do tej konkretnej oceny i wskazać na to thunk data structure. Silnik oceny zacznie działać tylko wtedy, gdy będzie to wymagane do oceny tego konkretnego wyrażenia.
Modularity- Aplikacja Haskell to nic innego jak szereg funkcji. Można powiedzieć, że aplikacja Haskell to zbiór wielu małych aplikacji Haskell.
Statically Typed- W konwencjonalnym języku programowania musimy zdefiniować szereg zmiennych wraz z ich typem. W przeciwieństwie do Haskell jest językiem ściśle maszynowym. Pod pojęciem języka ściśle typizowanego rozumiemy, że kompilator Haskell jest wystarczająco inteligentny, aby określić typ zadeklarowanej zmiennej, dlatego nie musimy wyraźnie wspominać o typie użytej zmiennej.
Maintainability - Aplikacje Haskell są modułowe, a zatem ich utrzymanie jest bardzo łatwe i opłacalne.
Programy funkcjonalne są bardziej współbieżne i wykonują je zgodnie z równoległością, aby zapewnić dokładniejszą i lepszą wydajność. Haskell nie jest wyjątkiem; został opracowany w sposób umożliwiający obsługęmultithreading efektywnie.
Witaj świecie
To prosty przykład pokazujący dynamizm Haskella. Spójrz na poniższy kod. Wszystko, czego potrzebujemy, to tylko jedna linia do wydrukowania „Hello Word” na konsoli.
main = putStrLn "Hello World"
Gdy kompilator Haskell napotka powyższy fragment kodu, natychmiast zwróci następujące dane wyjściowe -
Hello World
W tym samouczku udostępnimy wiele przykładów, aby pokazać moc i prostotę Haskell.
Skonfigurowaliśmy środowisko programistyczne Haskell online pod adresem - https://www.tutorialspoint.com/compile_haskell_online.php
Ten internetowy edytor ma wiele opcji do ćwiczenia przykładów programowania Haskell. Przejdź do sekcji terminala na stronie i wpisz"ghci". To polecenie automatycznie ładuje kompilator Haskell i uruchamia Haskell online. Otrzymasz następujące dane wyjściowe po użyciughci Komenda.
sh-4.3$ ghci
GHCi,version7.8.4:http://www.haskell.org/ghc/:?forhelp
Loading package ghc-prim...linking...done.
Loading packageinteger gmp...linking... done.
Loading package base...linking...done.
Prelude>
Jeśli nadal chcesz używać Haskell offline w swoim systemie lokalnym, musisz pobrać dostępną konfigurację Haskell z jego oficjalnej strony internetowej - https://www.haskell.org/downloads
Istnieją trzy różne typy plików installers dostępne na rynku -
Minimal Installer - Zapewnia narzędzia GHC (The Glasgow Haskell Compiler), CABAL (Common Architecture for Building Applications and Libraries) i Stack.
Stack Installer- W tym instalatorze GHC można pobrać na wielu platformach z zarządzanym łańcuchem opłat. Zainstaluje Twoją aplikację globalnie, tak aby w razie potrzeby zaktualizować swoje narzędzia API. Automatycznie rozwiązuje wszystkie zależności zorientowane na Haskella.
Haskell Platform- To najlepszy sposób na zainstalowanie Haskella, ponieważ zainstaluje on całą platformę na twoim komputerze i to z jednej określonej lokalizacji. Ten instalator nie jest dystrybucyjny, jak dwa powyższe instalatory.
Widzieliśmy różne typy instalatorów dostępnych na rynku, teraz zobaczmy, jak korzystać z tych instalatorów w naszej maszynie. W tym samouczku będziemy używać instalatora platformy Haskell, aby zainstalować kompilator Haskell w naszym systemie.
Środowisko skonfigurowane w systemie Windows
Aby skonfigurować środowisko Haskell na komputerze z systemem Windows, przejdź do ich oficjalnej strony internetowej https://www.haskell.org/platform/windows.html i pobierz instalator zgodnie z konfigurowalną architekturą.
Sprawdź architekturę systemu, pobierz odpowiedni plik instalacyjny i uruchom go. Zainstaluje się jak każda inna aplikacja Windows. Może zajść potrzeba zaktualizowania konfiguracji CABAL w systemie.
Konfiguracja środowiska w MAC
Aby skonfigurować środowisko Haskell w systemie MAC, przejdź do ich oficjalnej strony internetowej https://www.haskell.org/platform/mac.html i pobierz instalator dla komputerów Mac.
Środowisko skonfigurowane w systemie Linux
Instalacja Haskella w systemie opartym na Linuksie wymaga uruchomienia jakiegoś polecenia, które nie jest tak łatwe jak MAC i Windows. Tak, jest męczący, ale jest niezawodny.
Możesz wykonać kroki podane poniżej, aby zainstalować Haskell w systemie Linux -
Step 1 - Aby skonfigurować środowisko Haskell w systemie Linux, przejdź do oficjalnej strony internetowej https://www.haskell.org/platform/linux.htmli wybierz swoją dystrybucję. W swojej przeglądarce znajdziesz następujący ekran.
Step 2- Wybierz swoją dystrybucję. W naszym przypadku używamy Ubuntu. Po wybraniu tej opcji na ekranie pojawi się następująca strona z poleceniem instalacji Haskella w naszym systemie lokalnym.
Step 3 - Otwórz terminal, naciskając Ctrl + Alt + T. Uruchom polecenie "$ sudo apt-get install haskell-platform"i naciśnij Enter. Po uwierzytelnieniu za pomocą hasła roota automatycznie rozpocznie się pobieranie Haskell do twojego systemu. Po instalacji otrzymasz wiadomość potwierdzającą.
Step 4- Ponownie przejdź do terminala i uruchom polecenie GHCI. Gdy pojawi się zachęta Prelude, możesz używać Haskell w swoim lokalnym systemie.
Aby wyjść z prologu GHCI, możesz użyć polecenia „: quit exit”.
Haskell jest czysto funkcjonalnym językiem programowania, dlatego jest znacznie bardziej interaktywny i inteligentny niż inne języki programowania. W tym rozdziale poznamy podstawowe modele danych Haskella, które są w rzeczywistości wstępnie zdefiniowane lub w jakiś inteligentny sposób dekodowane do pamięci komputera.
W tym samouczku będziemy korzystać z platformy internetowej Haskell dostępnej na naszej stronie internetowej (https://www.tutorialspoint.com/codingground.htm).
Liczby
Haskell jest na tyle inteligentny, aby rozszyfrować jakąś liczbę jako liczbę. Dlatego nie musisz wymieniać jego typu na zewnątrz, jak to zwykle robimy w przypadku innych języków programowania. Jak na przykład, przejdź do wiersza poleceń preludium i po prostu uruchom "2 + 2" i naciśnij Enter.
sh-4.3$ ghci
GHCi, version 7.6.3: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> 2+2
W rezultacie otrzymasz następujące dane wyjściowe.
4
W powyższym kodzie właśnie przekazaliśmy dwie liczby jako argumenty do kompilatora GHCI bez uprzedniego zdefiniowania ich typu, ale kompilator mógł łatwo zdekodować te dwa wpisy jako liczby.
Spróbujmy teraz trochę bardziej skomplikowanych obliczeń matematycznych i zobaczmy, czy nasz inteligentny kompilator daje nam prawidłowe wyniki, czy nie. Spróbuj z „15+ (5 * 5) -40”
Prelude> 15+(5*5)-40
Powyższe wyrażenie daje „0” zgodnie z oczekiwanym wynikiem.
0
Postacie
Podobnie jak liczby, Haskell może inteligentnie zidentyfikować znak podany jako wejście do niego. Przejdź do wiersza poleceń Haskell i wpisz dowolny znak z podwójnym lub pojedynczym cudzysłowem.
Podajmy następujący wiersz jako wejście i sprawdźmy jego wyjście.
Prelude> :t "a"
Wytworzy następujący wynik -
"a" :: [Char]
Pamiętaj, że używasz (:t) podczas zasilania wejścia. W powyższym przykładzie(:t)polega na uwzględnieniu określonego typu związanego z danymi wejściowymi. Więcej o tym typie dowiemy się w kolejnych rozdziałach.
Spójrz na poniższy przykład, w którym przekazujemy nieprawidłowe dane wejściowe jako znak, co z kolei prowadzi do błędu.
Prelude> :t a
<interactive>:1:1: Not in scope: 'a'
Prelude> a
<interactive>:4:1: Not in scope: 'a'
Komunikatem o błędzie „<interactive>: 4: 1: Poza zakresem:„ a ”” kompilator Haskell ostrzega nas, że nie jest w stanie rozpoznać wprowadzonych danych. Haskell to rodzaj języka, w którym wszystko jest reprezentowane za pomocą liczby.
Haskell stosuje konwencjonalny styl kodowania ASCII. Spójrzmy na następujący przykład, aby zrozumieć więcej -
Prelude> '\97'
'a'
Prelude> '\67'
'C'
Zobacz, jak twoje dane wejściowe są dekodowane do formatu ASCII.
Strunowy
ZA stringto nic innego jak zbiór znaków. Nie ma określonej składni do używania łańcucha, ale Haskell stosuje konwencjonalny styl przedstawiania ciągu w podwójnym cudzysłowie.
Spójrz na poniższy przykład, w którym przekazujemy ciąg „Tutorialspoint.com”.
Prelude> :t "tutorialspoint.com"
Na ekranie wyświetli się następujący wynik -
"tutorialspoint.com" :: [Char]
Zobacz, jak cały ciąg został zdekodowany jako tablica tylko znaków Char. Przejdźmy do innego typu danych i jego składni. Gdy zaczniemy naszą rzeczywistą praktykę, przyzwyczaimy się do wszystkich typów danych i ich wykorzystania.
Boolean
Typ danych Boolean jest również dość prosty, podobnie jak inne typy danych. Spójrz na poniższy przykład, w którym użyjemy różnych operacji boolowskich przy użyciu niektórych danych wejściowych typu Boolean, takich jak „True” lub „False”.
Prelude> True && True
True
Prelude> True && False
False
Prelude> True || True
True
Prelude> True || False
True
W powyższym przykładzie nie musimy wspominać, że „True” i „False” to wartości boolowskie. Sam Haskell może go zdekodować i wykonać odpowiednie operacje. Zmodyfikujmy nasze dane wejściowe za pomocą „true” lub „false”.
Prelude> true
Wytworzy następujący wynik -
<interactive>:9:1: Not in scope: 'true'
W powyższym przykładzie Haskell nie mógł odróżnić „prawda” od wartości liczbowej, stąd nasze dane wejściowe „prawda” nie są liczbą. Dlatego kompilator Haskell zgłasza błąd informujący, że nasze dane wejściowe nie są jego zakresem.
Rozumienie listy i listy
Podobnie jak inne typy danych, Listjest również bardzo użytecznym typem danych używanym w Haskell. Na przykład [a, b, c] jest listą znaków, stąd z definicji Lista jest zbiorem danych tego samego typu, oddzielonych przecinkami.
Podobnie jak w przypadku innych typów danych, nie musisz deklarować listy jako listy. Haskell jest wystarczająco inteligentny, aby zdekodować twoje dane wejściowe, patrząc na składnię używaną w wyrażeniu.
Spójrz na poniższy przykład, który pokazuje, jak Haskell traktuje listę.
Prelude> [1,2,3,4,5]
Wytworzy następujący wynik -
[1,2,3,4,5]
Listy w Haskell są z natury jednorodne, co oznacza, że nie pozwalają na deklarowanie listy różnego rodzaju danych. Każda lista taka jak [1, 2, 3, 4, 5, a, b, c, d, e, f] spowoduje błąd.
Prelude> [1,2,3,4,5,a,b,c,d,e,f]
Ten kod spowoduje następujący błąd -
<interactive>:17:12: Not in scope: 'a'
<interactive>:17:14: Not in scope: 'b'
<interactive>:17:16: Not in scope: 'c'
<interactive>:17:18: Not in scope: 'd'
<interactive>:17:20: Not in scope: 'e'
<interactive>:17:22: Not in scope: 'f'
Rozumienie listy
Zrozumienie listy to proces tworzenia listy przy użyciu wyrażeń matematycznych. Spójrz na poniższy przykład, w którym tworzymy listę przy użyciu wyrażenia matematycznego w formacie [wyjście | zakres, stan].
Prelude> [x*2| x<-[1..10]]
[2,4,6,8,10,12,14,16,18,20]
Prelude> [x*2| x<-[1..5]]
[2,4,6,8,10]
Prelude> [x| x<-[1..5]]
[1,2,3,4,5]
Ta metoda tworzenia jednej listy przy użyciu wyrażenia matematycznego nosi nazwę List Comprehension.
Tuple
Haskell zapewnia inny sposób deklarowania wielu wartości w jednym typie danych. Jest znany jakoTuple. Krotka może być traktowana jako lista, jednak istnieją pewne techniczne różnice między krotką a listą.
Krotka jest niezmiennym typem danych, ponieważ nie możemy modyfikować liczby elementów w czasie wykonywania, podczas gdy lista jest zmiennym typem danych.
Z drugiej strony Lista jest jednorodnym typem danych, ale krotka jest z natury niejednorodna, ponieważ krotka może zawierać różne typy danych.
Krotki są reprezentowane przez pojedynczy nawias. Spójrz na poniższy przykład, aby zobaczyć, jak Haskell traktuje krotkę.
Prelude> (1,1,'a')
Wytworzy następujący wynik -
(1,1,'a')
W powyższym przykładzie użyliśmy jednej krotki z dwoma number zmienne typu, a char zmienna typu.
W tym rozdziale poznamy różne operatory używane w Haskell. Podobnie jak inne języki programowania, Haskell inteligentnie obsługuje podstawowe operacje, takie jak dodawanie, odejmowanie, mnożenie itp. W kolejnych rozdziałach dowiemy się więcej o różnych operatorach i ich zastosowaniu.
W tym rozdziale będziemy używać różnych operatorów w Haskell, korzystając z naszej platformy internetowej (https://www.tutorialspoint.com/codingground.htm). Pamiętaj, że używamy tylkointeger wpisz liczby, ponieważ dowiemy się więcej decimal wpisz numery w kolejnych rozdziałach.
Operator dodawania
Jak sama nazwa wskazuje, operator dodawania (+) służy do funkcji dodawania. Poniższy przykładowy kod pokazuje, jak dodać dwie liczby całkowite w Haskell -
main = do
let var1 = 2
let var2 = 3
putStrLn "The addition of the two numbers is:"
print(var1 + var2)
W powyższym pliku stworzyliśmy dwie oddzielne zmienne var1 i var2. Na koniec drukujemy wynik za pomocąadditionoperator. Użyjcompile i execute przycisk, aby uruchomić kod.
Ten kod spowoduje wyświetlenie na ekranie następującego wyniku -
The addition of the two numbers is:
5
Operator odejmowania
Jak sama nazwa wskazuje, ten operator jest używany do odejmowania. Poniższy przykładowy kod pokazuje, jak odjąć dwie liczby całkowite w Haskell -
main = do
let var1 = 10
let var2 = 6
putStrLn "The Subtraction of the two numbers is:"
print(var1 - var2)
W tym przykładzie utworzyliśmy dwie zmienne var1 i var2. Następnie używamy operatora odejmowania (-), aby odjąć dwie wartości.
Ten kod spowoduje wyświetlenie na ekranie następującego wyniku -
The Subtraction of the two numbers is:
4
Operator mnożenia
Ten operator jest używany do operacji mnożenia. Poniższy kod pokazuje, jak pomnożyć dwie liczby w Haskell za pomocą operatora mnożenia -
main = do
let var1 = 2
let var2 = 3
putStrLn "The Multiplication of the Two Numbers is:"
print(var1 * var2)
Ten kod wygeneruje następujące dane wyjściowe, gdy uruchomisz go na naszej platformie online -
The Multiplication of the Two Numbers is:
6
Operator dywizji
Spójrz na poniższy kod. Pokazuje, jak możesz podzielić dwie liczby w Haskell -
main = do
let var1 = 12
let var2 = 3
putStrLn "The Division of the Two Numbers is:"
print(var1/var2)
Wytworzy następujący wynik -
The Division of the Two Numbers is:
4.0
Operator sekwencji / zakresu
Sekwencja lub zakres to operator specjalny w Haskell. Jest oznaczony jako „(..)”. Możesz użyć tego operatora podczas deklarowania listy z sekwencją wartości.
Jeśli chcesz wydrukować wszystkie wartości od 1 do 10, możesz użyć czegoś takiego jak „[1..10]”. Podobnie, jeśli chcesz wygenerować wszystkie alfabety od „a” do „z”, możesz po prostu wpisać"[a..z]".
Poniższy kod pokazuje, jak można użyć operatora Sequence do wydrukowania wszystkich wartości od 1 do 10 -
main :: IO()
main = do
print [1..10]
Wygeneruje następujące dane wyjściowe -
[1,2,3,4,5,6,7,8,9,10]
Podejmowanie decyzji to funkcja, która pozwala programistom zastosować warunek w przepływie kodu. Programista może wykonać zestaw instrukcji w zależności od predefiniowanego warunku. Poniższy schemat blokowy przedstawia strukturę podejmowania decyzji przez Haskell -
Haskell zapewnia następujące typy oświadczeń decyzyjnych -
Sr.No. | Oświadczenie i opis |
---|---|
1 | if – else oświadczenie Jeden if instrukcja z rozszerzeniem elsekomunikat. Instrukcja welse blok zostanie wykonany tylko wtedy, gdy podany warunek boolowski nie zostanie spełniony. |
2 | Zagnieżdżona instrukcja if-else Wielokrotność if bloki, po których następuje else Bloki |
Haskell jest językiem funkcjonalnym i jest ściśle typizowany, co oznacza, że typ danych używany w całej aplikacji będzie znany kompilatorowi w czasie kompilacji.
Wbudowana klasa typu
W Haskell każde zdanie jest uważane za wyrażenie matematyczne, a kategoria tego wyrażenia jest nazywana jako Type. Można powiedzieć, że „Typ” jest typem danych wyrażenia używanego w czasie kompilacji.
Aby dowiedzieć się więcej o Type, użyjemy polecenia „: t”. W ogólny sposóbType można uznać za wartość, podczas gdy Type Classmożna traktować jako zbiór podobnych typów. W tym rozdziale poznamy różne typy wbudowane.
Int
Intjest klasą typu reprezentującą dane typu Integer. Każda liczba całkowita z zakresu od 2147483647 do -2147483647 jest objęta rozszerzeniemIntklasa typu. W poniższym przykładzie funkcjafType() będzie zachowywać się zgodnie ze zdefiniowanym typem.
fType :: Int -> Int -> Int
fType x y = x*x + y*y
main = print (fType 2 4)
Tutaj ustawiliśmy typ funkcji fType() tak jak int. Funkcja zajmuje dwaint wartości i zwraca jeden intwartość. Jeśli skompilujesz i wykonasz ten fragment kodu, wygeneruje on następujący wynik -
sh-4.3$ ghc -O2 --make *.hs -o main -threaded -rtsopts
sh-4.3$ main
20
Liczba całkowita
Integer można uznać za nadzbiór Int. Ta wartość nie jest ograniczona żadną liczbą, dlatego liczba całkowita może mieć dowolną długość bez żadnych ograniczeń. Aby zobaczyć podstawową różnicę międzyInt i Integer typów, zmodyfikujmy powyższy kod w następujący sposób -
fType :: Int -> Int -> Int
fType x y = x*x + y*y
main = print (fType 212124454 44545454454554545445454544545)
Jeśli skompilujesz powyższy fragment kodu, zostanie wyrzucony następujący komunikat o błędzie -
main.hs:3:31: Warning:
Literal 44545454454554545445454544545 is out of the Int range -
9223372036854775808..9223372036854775807
Linking main ...
Ten błąd wystąpił, ponieważ nasza funkcja fType () oczekiwała jednej wartości typu Int i przekazujemy naprawdę dużą wartość typu Int. Aby uniknąć tego błędu, zmieńmy typ „Int” na „Integer” i obserwujmy różnicę.
fType :: Integer -> Integer -> Integer
fType x y = x*x + y*y
main = print (fType 212124454 4454545445455454545445445454544545)
Teraz wygeneruje następujący wynik -
sh-4.3$ main
1984297512562793395882644631364297686099210302577374055141
Pływak
Spójrz na następujący fragment kodu. Pokazuje, jak działa typ Float w Haskell -
fType :: Float -> Float -> Float
fType x y = x*x + y*y
main = print (fType 2.5 3.8)
Funkcja przyjmuje dwie wartości zmiennoprzecinkowe jako dane wejściowe i zwraca inną wartość zmiennoprzecinkową jako dane wyjściowe. Kiedy kompilujesz i wykonujesz ten kod, wygeneruje on następujące dane wyjściowe -
sh-4.3$ main
20.689999
Podwójnie
Doublejest liczbą zmiennoprzecinkową z podwójną precyzją na końcu. Spójrz na następujący przykład -
fType :: Double -> Double -> Double
fType x y = x*x + y*y
main = print (fType 2.56 3.81)
Po wykonaniu powyższego fragmentu kodu wygeneruje on następujące dane wyjściowe -
sh-4.3$ main
21.0697
Bool
Booljest typem boolowskim. Może mieć wartość True lub False. Wykonaj następujący kod, aby zrozumieć, jak działa typ Bool w Haskell -
main = do
let x = True
if x == False
then putStrLn "X matches with Bool Type"
else putStrLn "X is not a Bool Type"
Tutaj definiujemy zmienną „x” jako wartość logiczną i porównujemy ją z inną wartością logiczną, aby sprawdzić jej oryginalność. Wytworzy następujący wynik -
sh-4.3$ main
X is not a Bool Type
Zwęglać
Znaki reprezentują postacie. Cokolwiek w pojedynczym cudzysłowie jest traktowane jako znak. W poniższym kodzie zmodyfikowaliśmy nasz poprzedni plikfType() funkcja, aby zaakceptować wartość Char i zwrócić wartość Char jako dane wyjściowe.
fType :: Char-> Char
fType x = 'K'
main = do
let x = 'v'
print (fType x)
Zadzwoni powyższy fragment kodu fType() funkcja z charwartość „v”, ale zwraca inną wartość znakową, czyli „K”. Oto jego wynik -
sh-4.3$ main
'K'
Zauważ, że nie będziemy używać tych typów jawnie, ponieważ Haskell jest wystarczająco inteligentny, aby złapać typ przed jego zadeklarowaniem. W kolejnych rozdziałach tego samouczka zobaczymy, jak różne typy i klasy Type sprawiają, że Haskell jest językiem silnie typizowanym.
Klasa typu EQ
EQklasa typu to interfejs, który udostępnia funkcję testowania równości wyrażenia. Każda klasa Type, która chce sprawdzić równość wyrażenia, powinna być częścią tej klasy EQ Type.
Częścią tego są wszystkie wymienione powyżej standardowe klasy typów EQklasa. Ilekroć sprawdzamy jakąkolwiek równość przy użyciu dowolnego z typów wymienionych powyżej, w rzeczywistości wykonujemy wywołanieEQ klasa typu.
W poniższym przykładzie używamy EQ Wpisz wewnętrznie, używając operacji „==” lub „/ =”.
main = do
if 8 /= 8
then putStrLn "The values are Equal"
else putStrLn "The values are not Equal"
To da następujący wynik -
sh-4.3$ main
The values are not Equal
Klasa typu zamówienia
Ordto kolejna klasa interfejsu, która daje nam funkcjonalność zamawiania. Wszystkietypes których używaliśmy do tej pory, są tego częścią Ordberło. Podobnie jak interfejs EQ, interfejs Ord można wywołać za pomocą ">", "<", "<=", "> =", "porównaj".
Poniżej znajduje się przykład, w którym użyliśmy funkcji „porównaj” tej klasy typu.
main = print (4 <= 2)
W tym przypadku kompilator Haskell sprawdzi, czy 4 jest mniejsze lub równe 2. Ponieważ tak nie jest, kod zwróci następujący wynik -
sh-4.3$ main
False
Pokazać
Showma funkcję wypisywania argumentu jako String. Cokolwiek może być jego argumentem, zawsze wypisuje wynik jako String. W poniższym przykładzie wydrukujemy całą listę za pomocą tego interfejsu. Do wywołania tego interfejsu można użyć „show”.
main = print (show [1..10])
Na konsoli wygeneruje następujące dane wyjściowe. Tutaj podwójne cudzysłowy wskazują, że jest to wartość typu String.
sh-4.3$ main
"[1,2,3,4,5,6,7,8,9,10]"
Czytać
Readinterfejs robi to samo co Show, ale nie wypisze wyniku w formacie String. W poniższym kodzie użyliśmyread interfejs, aby odczytać wartość ciągu i przekonwertować ją na wartość Int.
main = print (readInt "12")
readInt :: String -> Int
readInt = read
Tutaj przekazujemy zmienną typu String („12”) do pliku readInt, która z kolei zwraca 12 (wartość Int) po konwersji. Oto jego wynik -
sh-4.3$ main
12
Enum
Enumto kolejny typ klasy Type, który umożliwia sekwencyjną lub uporządkowaną funkcjonalność w Haskell. Dostęp do tej klasy Type można uzyskać za pomocą poleceń, takich jakSucc, Pred, Bool, Charitp.
Poniższy kod pokazuje, jak znaleźć następną wartość 12.
main = print (succ 12)
Wytworzy następujący wynik -
sh-4.3$ main
13
Zobowiązany
Wszystkie typy posiadające górną i dolną granicę należą do tej klasy typów. Na przykład,Int typ danych ma maksymalną granicę „9223372036854775807” i minimalną granicę „-9223372036854775808”.
Poniższy kod pokazuje, jak Haskell określa maksymalne i minimalne ograniczenie typu Int.
main = do
print (maxBound :: Int)
print (minBound :: Int)
Wytworzy następujący wynik -
sh-4.3$ main
9223372036854775807
-9223372036854775808
Teraz spróbuj znaleźć maksymalne i minimalne ograniczenie typów Char, Float i Bool.
Num
Ta klasa typu jest używana do operacji numerycznych. Typy, takie jak Int, Integer, Float i Double, należą do tej klasy Type. Spójrz na następujący kod -
main = do
print(2 :: Int)
print(2 :: Float)
Wytworzy następujący wynik -
sh-4.3$ main
2
2.0
Całka
Integralmożna uznać za podklasę klasy Num Type. Klasa Num Type zawiera wszystkie typy liczb, natomiast klasa Integral jest używana tylko dla liczb całkowitych. Int i Integer to typy w tej klasie Type.
Ruchomy
Podobnie jak Integral, Floating jest również częścią klasy Num Type, ale przechowuje tylko liczby zmiennoprzecinkowe. W związku z tym,Float i Double należą do tej klasy typu.
Klasa typu niestandardowego
Jak każdy inny język programowania, Haskell umożliwia programistom definiowanie typów zdefiniowanych przez użytkownika. W poniższym przykładzie utworzymy typ zdefiniowany przez użytkownika i użyjemy go.
data Area = Circle Float Float Float
surface :: Area -> Float
surface (Circle _ _ r) = pi * r ^ 2
main = print (surface $ Circle 10 20 10 )
Tutaj stworzyliśmy nowy typ o nazwie Area. Następnie używamy tego typu do obliczenia pola koła. W powyższym przykładzie „powierzchnia” jest funkcją, która przyjmujeArea jako wkład i produkuje Float jako wyjście.
Należy pamiętać, że „dane” jest tutaj słowem kluczowym, a wszystkie typy zdefiniowane przez użytkownika w Haskell zawsze zaczynają się od dużej litery.
Wytworzy następujący wynik -
sh-4.3$ main
314.15927
Funkcje odgrywają główną rolę w Haskell, ponieważ jest to funkcjonalny język programowania. Podobnie jak inne języki, Haskell ma własną funkcjonalną definicję i deklarację.
Deklaracja funkcji składa się z nazwy funkcji i jej listy argumentów wraz z danymi wyjściowymi.
Definicja funkcji to miejsce, w którym faktycznie definiujesz funkcję.
Weźmy mały przykład add funkcji szczegółowego zrozumienia tej koncepcji.
add :: Integer -> Integer -> Integer --function declaration
add x y = x + y --function definition
main = do
putStrLn "The addition of the two numbers is:"
print(add 2 5) --calling a function
Tutaj zadeklarowaliśmy naszą funkcję w pierwszym wierszu, aw drugim napisaliśmy naszą aktualną funkcję, która pobierze dwa argumenty i wygeneruje jedno wyjście typu całkowitego.
Podobnie jak większość innych języków, Haskell zaczyna kompilować kod z mainmetoda. Nasz kod wygeneruje następujący wynik -
The addition of the two numbers is:
7
Dopasowywanie wzorców
Dopasowywanie wzorców to proces dopasowywania określonego typu wyrażeń. To nic innego jak technika upraszczania kodu. Technikę tę można zaimplementować w dowolnym typie klasy Type. Jeśli-Else może być używane jako alternatywna opcja dopasowania wzorców.
Dopasowywanie wzorców można traktować jako wariant dynamicznego polimorfizmu, w którym w czasie wykonywania różne metody mogą być wykonywane w zależności od ich listy argumentów.
Spójrz na następujący blok kodu. Tutaj użyliśmy techniki dopasowania wzorców, aby obliczyć silnię liczby.
fact :: Int -> Int
fact 0 = 1
fact n = n * fact ( n - 1 )
main = do
putStrLn "The factorial of 5 is:"
print (fact 5)
Wszyscy wiemy, jak obliczyć silnię liczby. Kompilator rozpocznie wyszukiwanie funkcji o nazwie „fakt” z argumentem. Jeśli argument nie jest równy 0, to liczba będzie nadal wywoływać tę samą funkcję z 1 mniejszą od rzeczywistego argumentu.
Kiedy wzorzec argumentu dokładnie pasuje do 0, wywoła nasz wzorzec, który ma postać „fakt 0 = 1”. Nasz kod wygeneruje następujący wynik -
The factorial of 5 is:
120
Gwardia
Guardsto koncepcja bardzo podobna do dopasowywania wzorców. W dopasowywaniu wzorców zwykle dopasowujemy jedno lub więcej wyrażeń, ale używamyguards aby przetestować jakąś właściwość wyrażenia.
Chociaż zaleca się użycie dopasowania wzorców ponad guards, ale z perspektywy dewelopera guardsjest bardziej czytelny i prosty. Dla początkujących użytkownikówguards mogą wyglądać bardzo podobnie do instrukcji Jeśli-Inaczej, ale różnią się funkcjonalnie.
W poniższym kodzie zmodyfikowaliśmy nasze factorial program przy użyciu koncepcji guards.
fact :: Integer -> Integer
fact n | n == 0 = 1
| n /= 0 = n * fact (n-1)
main = do
putStrLn "The factorial of 5 is:"
print (fact 5)
Tutaj zadeklarowaliśmy dwa guardsrozdzielone znakiem „|” i dzwoniąc dofact funkcja z main. Wewnętrznie kompilator będzie działał w taki sam sposób, jak w przypadku dopasowywania wzorców, aby uzyskać następujący wynik -
The factorial of 5 is:
120
Where Clause
Whereto słowo kluczowe lub wbudowana funkcja, której można użyć w czasie wykonywania do wygenerowania żądanego wyniku. Może to być bardzo pomocne, gdy obliczenia funkcji stają się skomplikowane.
Rozważmy scenariusz, w którym dane wejściowe to złożone wyrażenie z wieloma parametrami. W takich przypadkach możesz podzielić całe wyrażenie na małe części, używając klauzuli „where”.
W poniższym przykładzie bierzemy złożone wyrażenie matematyczne. Pokażemy, jak znaleźć pierwiastki równania wielomianowego [x ^ 2 - 8x + 6] za pomocą Haskella.
roots :: (Float, Float, Float) -> (Float, Float)
roots (a,b,c) = (x1, x2) where
x1 = e + sqrt d / (2 * a)
x2 = e - sqrt d / (2 * a)
d = b * b - 4 * a * c
e = - b / (2 * a)
main = do
putStrLn "The roots of our Polynomial equation are:"
print (roots(1,-8,6))
Zwróć uwagę na złożoność naszego wyrażenia do obliczenia pierwiastków podanej funkcji wielomianu. To jest dość złożone. W związku z tym łamiemy wyrażenie za pomocąwhereklauzula. Powyższy fragment kodu wygeneruje następujące dane wyjściowe -
The roots of our Polynomial equation are:
(7.1622777,0.8377223)
Funkcja rekurencyjna
Rekurencja to sytuacja, w której funkcja wielokrotnie wywołuje samą siebie. Haskell nie zapewnia żadnej możliwości zapętlenia żadnego wyrażenia więcej niż raz. Zamiast tego Haskell chce, abyś rozbił całą swoją funkcjonalność na zbiór różnych funkcji i wykorzystał technikę rekurencji do zaimplementowania swojej funkcjonalności.
Rozważmy ponownie nasz przykład dopasowania wzorca, w którym obliczyliśmy silnię liczby. Znajdowanie silni liczby jest klasycznym przypadkiem użycia rekursji. Tutaj możesz: „W jaki sposób dopasowanie wzorca różni się od rekurencji?” Różnica między tymi dwoma polega na sposobie ich użycia: Dopasowanie wzorców działa na ustawieniu ograniczenia terminala, podczas gdy rekurencja jest wywołaniem funkcji.
W poniższym przykładzie użyliśmy dopasowania wzorców i rekurencji do obliczenia silni 5.
fact :: Int -> Int
fact 0 = 1
fact n = n * fact ( n - 1 )
main = do
putStrLn "The factorial of 5 is:"
print (fact 5)
Wytworzy następujący wynik -
The factorial of 5 is:
120
Funkcja wyższego rzędu
Do tej pory widzieliśmy, że funkcje Haskella przyjmują jeden type jako wkład i wyprodukuj inny typejako wyjście, które jest bardzo podobne w innych językach imperatywnych. Funkcje wyższego rzędu są unikalną cechą Haskella, w której można używać funkcji jako argumentu wejściowego lub wyjściowego.
Chociaż jest to koncepcja wirtualna, ale w rzeczywistych programach każda funkcja, którą definiujemy w Haskell, wykorzystuje mechanizm wyższego rzędu do dostarczania danych wyjściowych. Jeśli przyjrzysz się funkcji bibliotecznej Haskella, zobaczysz, że większość funkcji bibliotecznych została napisana w sposób wyższego rzędu.
Weźmy przykład, w którym zaimportujemy wbudowaną mapę funkcji wyższego rzędu i użyjemy jej do zaimplementowania innej funkcji wyższego rzędu zgodnie z naszym wyborem.
import Data.Char
import Prelude hiding (map)
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map func (x : abc) = func x : map func abc
main = print $ map toUpper "tutorialspoint.com"
W powyższym przykładzie użyliśmy toUpper funkcja klasy typu Charaby zamienić nasze dane wejściowe na wielkie litery. W tym przypadku metoda „map” przyjmuje funkcję jako argument i zwraca wymagane wyjście. Oto jego wynik -
sh-4.3$ ghc -O2 --make *.hs -o main -threaded -rtsopts sh-4.3$ main
"TUTORIALSPOINT.COM"
Wyrażenie lambda
Czasami musimy napisać funkcję, która będzie używana tylko raz, przez cały okres użytkowania aplikacji. Aby poradzić sobie z tego rodzaju sytuacjami, programiści Haskell używają innego anonimowego bloku znanego jakolambda expression lub lambda function.
Funkcja bez definicji nazywana jest funkcją lambda. Funkcja lambda jest oznaczona znakiem „\”. Weźmy następujący przykład, w którym zwiększymy wartość wejściową o 1 bez tworzenia żadnej funkcji.
main = do
putStrLn "The successor of 4 is:"
print ((\x -> x + 1) 4)
Tutaj stworzyliśmy anonimową funkcję, która nie ma nazwy. Przyjmuje liczbę całkowitą 4 jako argument i wyświetla wartość wyjściową. Zasadniczo obsługujemy jedną funkcję, nawet nie deklarując jej poprawnie. Na tym polega piękno wyrażeń lambda.
Nasze wyrażenie lambda da następujący wynik -
sh-4.3$ main
The successor of 4 is:
5
Do tej pory omówiliśmy wiele typów funkcji Haskella i używaliśmy różnych sposobów wywoływania tych funkcji. W tym rozdziale dowiemy się o kilku podstawowych funkcjach, których można łatwo używać w Haskell bez importowania żadnej specjalnej klasy Type. Większość z tych funkcji jest częścią innych funkcji wyższego rzędu.
Funkcja głowy
Headfunkcja działa na liście. Zwraca pierwszy argument wejściowy, który jest w zasadzie listą. W poniższym przykładzie przekazujemy listę z 10 wartościami i generujemy pierwszy element tej listy przy użyciuhead funkcjonować.
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "The first element of the list is:"
print (head x)
Wytworzy następujący wynik -
Our list is:
[1,2,3,4,5,6,7,8,9,10]
The first element of the list is:
1
Funkcja ogona
Tail jest funkcją, która uzupełnia headfunkcjonować. To zajmujelistjako dane wejściowe i zwraca całą listę bez części głównej. Oznacza to, żetailfunkcja zwraca całą listę bez pierwszego elementu. Spójrz na następujący przykład -
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "The tail of our list is:"
print (tail x)
Wytworzy następujący wynik -
Our list is:
[1,2,3,4,5,6,7,8,9,10]
The tail of our list is:
[2,3,4,5,6,7,8,9,10]
Ostatnia funkcja
Jak sama nazwa wskazuje, zwraca ostatni element listy, który jest dostarczany jako dane wejściowe. Sprawdź poniższy przykład.
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "The last element of our list is:"
print (last x)
Wytworzy następujący wynik -
Our list is:
[1,2,3,4,5,6,7,8,9,10]
The last element of our list is:
10
Funkcja Init
Init działa dokładnie tak samo, jak przeciwieństwo tailfunkcjonować. Pobiera listę jako argument i zwraca całą listę bez ostatniego wpisu.
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "Our list without the last entry:"
print (init x)
Teraz obserwuj jego wyniki -
Our list is:
[1,2,3,4,5,6,7,8,9,10]
Our list without the last entry:
[1,2,3,4,5,6,7,8,9]
Funkcja zerowa
Null jest logiczną funkcją sprawdzającą, która działa na łańcuchu i zwraca True tylko wtedy, gdy podana lista jest pusta, w przeciwnym razie zwraca False. Poniższy kod sprawdza, czy podana lista jest pusta, czy nie.
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "Is our list empty?"
print (null x)
Wytworzy następujący wynik -
Our list is:
[1,2,3,4,5,6,7,8,9,10]
Is our list empty?
False
Funkcja odwrotna
Działa na wejściu typu String i konwertuje całe wejście w odwrotnej kolejności, dając w rezultacie jedno wyjście. Poniżej znajduje się podstawa kodu dla tej funkcji.
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "The list in Reverse Order is:"
print (reverse x)
Wytworzy następujący wynik -
Our list is:
[1,2,3,4,5,6,7,8,9,10]
The list in Reverse Order is:
[10,9,8,7,6,5,4,3,2,1]
Funkcja długości
Ta funkcja służy do obliczania długości pliku listpodany jako argument. Spójrz na następujący przykład -
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "The length of this list is:"
print (length x)
Mamy 10 elementów na naszej liście, stąd nasz kod zwróci 10 jako wynik.
Our list is:
[1,2,3,4,5,6,7,8,9,10]
The length of this list is:
10
Weź funkcję
Takefunkcja służy do tworzenia podłańcucha z innego ciągu. Poniższy kod pokazuje, jak można użyć funkcji take w Haskell -
main = print(take 5 ([1 .. 10]))
Kod generuje podciąg zawierający 5 elementów z dostarczonej listy -
[1,2,3,4,5]
Funkcja upuszczania
Ta funkcja jest również używana do generowania podłańcucha. Działa jako przeciwieństwotakefunkcjonować. Spójrz na następujący fragment kodu -
main = print(drop 5 ([1 .. 10]))
Kod usuwa pierwsze 5 elementów z dostarczonej listy i drukuje pozostałe 5 elementów. Wytworzy następujący wynik -
[6,7,8,9,10]
Maksymalna funkcja
Ta funkcja służy do znalezienia elementu o maksymalnej wartości z dostarczonej listy. Zobaczmy, jak to wykorzystać w praktyce -
main = do
let x = [1,45,565,1245,02,2]
putStrLn "The maximum value element of the list is:"
print (maximum x)
Powyższy fragment kodu wygeneruje następujące dane wyjściowe -
The maximum value element of the list is:
1245
Minimalna funkcja
Ta funkcja służy do znalezienia elementu o minimalnej wartości z dostarczonej listy. To po prostu przeciwieństwomaximum funkcjonować.
main = do
let x = [1,45,565,1245,02,2]
putStrLn "The minimum value element of the list is:"
print (minimum x)
Wynik powyższego kodu to -
The minimum value element of the list is:
1
Funkcja sumy
Jak sama nazwa wskazuje, funkcja ta zwraca sumę wszystkich elementów znajdujących się na podanej liście. Poniższy kod pobiera listę 5 elementów i zwraca ich sumę jako dane wyjściowe.
main = do
let x = [1..5]
putStrLn "Our list is:"
print (x)
putStrLn "The summation of the list elements is:"
print (sum x)
Wytworzy następujący wynik -
Our list is:
[1,2,3,4,5]
The summation of the list elements is:
15
Funkcja produktu
Możesz użyć tej funkcji, aby pomnożyć wszystkie elementy listy i wydrukować jej wartość.
main = do
let x = [1..5]
putStrLn "Our list is:"
print (x)
putStrLn "The multiplication of the list elements is:"
print (product x)
Nasz kod wygeneruje następujący wynik -
Our list is:
[1,2,3,4,5]
The multiplication of the list elements is:
120
Funkcja Elem
Ta funkcja służy do sprawdzania, czy dostarczona lista zawiera określony element, czy nie. W związku z tym zwraca atrue lub a false.
Poniższy kod sprawdza, czy podana lista elementów zawiera wartość 786.
main = do
let x = [1,45,155,1785]
putStrLn "Our list is:"
print (x)
putStrLn "Does it contain 786?"
print (elem 786 (x))
Wytworzy następujący wynik -
Our list is:
[1,45,155,1785]
Does it contain 786?
False
Użyj tego samego kodu, aby sprawdzić, czy dostarczona lista zawiera wartość 1785, czy nie.
Function Compositionjest procesem wykorzystywania danych wyjściowych jednej funkcji jako danych wejściowych innej funkcji. Będzie lepiej, jeśli nauczymy się matematyki stojącej za niącomposition. W matematyce,composition jest oznaczony przez f{g(x)} gdzie g() jest funkcją, a jej wyjście jest używane jako dane wejściowe innej funkcji, to znaczy f().
Kompozycję funkcji można zaimplementować przy użyciu dowolnych dwóch funkcji, pod warunkiem, że typ wyjścia jednej funkcji jest zgodny z typem wejścia drugiej funkcji. Używamy operatora kropki (.) Do implementacji kompozycji funkcji w Haskell.
Spójrz na poniższy przykładowy kod. Tutaj użyliśmy kompozycji funkcji do obliczenia, czy liczba wejściowa jest parzysta, czy nieparzysta.
eveno :: Int -> Bool
noto :: Bool -> String
eveno x = if x `rem` 2 == 0
then True
else False
noto x = if x == True
then "This is an even Number"
else "This is an ODD number"
main = do
putStrLn "Example of Haskell Function composition"
print ((noto.eveno)(16))
Tutaj, w main funkcji, wywołujemy dwie funkcje, noto i eveno, równocześnie. Kompilator najpierw wywoła funkcję"eveno()" z 16jako argument. Następnie kompilator użyje danych wyjściowycheveno metoda jako dane wejściowe noto() metoda.
Jego wynik byłby następujący -
Example of Haskell Function composition
"This is an even Number"
Ponieważ podajemy liczbę 16 jako dane wejściowe (która jest liczbą parzystą), funkcja eveno() funkcja zwraca true, który staje się danymi wejściowymi dla noto() funkcji i zwraca wynik: „To jest liczba parzysta”.
Jeśli pracowałeś nad Javą, wiesz, jak wszystkie klasy są powiązane z folderem o nazwie package. Podobnie, Haskell można uznać za zbiór plikówmodules.
Haskell jest językiem funkcjonalnym i wszystko jest oznaczone jako wyrażenie, stąd moduł można nazwać zbiorem podobnych lub powiązanych typów funkcji.
Możesz importfunkcja z jednego modułu do innego modułu. Wszystkie instrukcje „import” powinny znajdować się na pierwszym miejscu, zanim zaczniesz definiować inne funkcje. W tym rozdziale poznamy różne funkcje modułów Haskell.
Moduł listy
List udostępnia kilka wspaniałych funkcji do pracy listtyp danych. Po zaimportowaniu modułu Lista masz do dyspozycji szeroki wachlarz funkcji.
W poniższym przykładzie wykorzystaliśmy kilka ważnych funkcji dostępnych w module List.
import Data.List
main = do
putStrLn("Different methods of List Module")
print(intersperse '.' "Tutorialspoint.com")
print(intercalate " " ["Lets","Start","with","Haskell"])
print(splitAt 7 "HaskellTutorial")
print (sort [8,5,3,2,1,6,4,2])
Tutaj mamy wiele funkcji, nawet ich nie definiując. Dzieje się tak, ponieważ te funkcje są dostępne w module Lista. Po zaimportowaniu modułu List, kompilator Haskell udostępnił wszystkie te funkcje w globalnej przestrzeni nazw. Dlatego moglibyśmy użyć tych funkcji.
Nasz kod da następujący wynik -
Different methods of List Module
"T.u.t.o.r.i.a.l.s.p.o.i.n.t...c.o.m"
"Lets Start with Haskell"
("Haskell","Tutorial")
[1,2,2,3,4,5,6,8]
Moduł Char
Plik Charmoduł posiada wiele predefiniowanych funkcji do pracy z typem Character. Spójrz na następujący blok kodu -
import Data.Char
main = do
putStrLn("Different methods of Char Module")
print(toUpper 'a')
print(words "Let us study tonight")
print(toLower 'A')
Tutaj funkcje toUpper i toLower są już zdefiniowane w Charmoduł. Wytworzy następujący wynik -
Different methods of Char Module
'A'
["Let","us","study","tonight"]
'a'
Moduł mapy
Mapjest nieposortowanym typem danych typu para wartość dodana. Jest to szeroko stosowany moduł z wieloma przydatnymi funkcjami. Poniższy przykład pokazuje, jak można użyć predefiniowanej funkcji dostępnej w module Map.
import Data.Map (Map)
import qualified Data.Map as Map --required for GHCI
myMap :: Integer -> Map Integer [Integer]
myMap n = Map.fromList (map makePair [1..n])
where makePair x = (x, [x])
main = print(myMap 3)
Wytworzy następujący wynik -
fromList [(1,[1]),(2,[2]),(3,[3])]
Ustaw moduł
Moduł Set ma kilka bardzo przydatnych predefiniowanych funkcji do manipulowania danymi matematycznymi. Zestaw jest implementowany jako drzewo binarne, więc wszystkie elementy w zestawie muszą być unikalne.
Spójrz na poniższy przykładowy kod
import qualified Data.Set as Set
text1 = "Hey buddy"
text2 = "This tutorial is for Haskell"
main = do
let set1 = Set.fromList text1
set2 = Set.fromList text2
print(set1)
print(set2)
Tutaj modyfikujemy ciąg znaków w zestaw. Wytworzy następujący wynik. Zwróć uwagę, że zestaw wyjściowy nie zawiera powtórzeń znaków.
fromList " Hbdeuy"
fromList " HTaefhiklorstu"
Moduł niestandardowy
Zobaczmy, jak możemy stworzyć niestandardowy moduł, który można wywołać w innych programach. Aby zaimplementować ten niestandardowy moduł, utworzymy oddzielny plik o nazwie"custom.hs" wraz z naszym "main.hs".
Stwórzmy niestandardowy moduł i zdefiniujmy w nim kilka funkcji.
custom.hs
module Custom (
showEven,
showBoolean
) where
showEven:: Int-> Bool
showEven x = do
if x 'rem' 2 == 0
then True
else False
showBoolean :: Bool->Int
showBoolean c = do
if c == True
then 1
else 0
Nasz moduł Custom jest gotowy. Teraz zaimportujmy go do programu.
main.hs
import Custom
main = do
print(showEven 4)
print(showBoolean True)
Nasz kod wygeneruje następujący wynik -
True
1
Plik showEven funkcja zwraca True, ponieważ „4” to liczba parzysta. PlikshowBoolean funkcja zwraca „1”, ponieważ funkcja boolowska, którą przekazaliśmy do funkcji, ma wartość „True”.
Wszystkie przykłady, które omówiliśmy do tej pory, mają charakter statyczny. W tym rozdziale nauczymy się dynamicznie komunikować się z użytkownikami. Nauczymy się różnych technik wejścia i wyjścia stosowanych w Haskell.
Pliki i strumienie
Do tej pory zakodowaliśmy na stałe wszystkie dane wejściowe w samym programie. Pobieraliśmy dane wejściowe ze zmiennych statycznych. Teraz nauczmy się czytać i pisać z zewnętrznego pliku.
Utwórzmy plik i nazwijmy go „abc.txt”. Następnie wprowadź następujące wiersze w tym pliku tekstowym: „Witamy w Tutorialspoint. Tutaj uzyskasz najlepsze źródło wiedzy o Haskellu”.
Następnie napiszemy następujący kod, który wyświetli zawartość tego pliku na konsoli. Tutaj używamy funkcji readFile (), która czyta plik, dopóki nie znajdzie znaku EOF.
main = do
let file = "abc.txt"
contents <- readFile file
putStrLn contents
Powyższy fragment kodu odczyta plik „abc.txt” jako ciąg znaków, dopóki nie napotka dowolnego znaku końca pliku. Ten fragment kodu wygeneruje następujące dane wyjściowe.
Welcome to Tutorialspoint
Here, you will get the best resource to learn Haskell.
Zwróć uwagę, że cokolwiek wypisuje na terminalu, jest zapisane w tym pliku.
Argument wiersza poleceń
Haskell zapewnia również możliwość obsługi pliku za pomocą wiersza poleceń. Wróćmy do naszego terminala i wpiszmy"ghci". Następnie wpisz następujący zestaw poleceń -
let file = "abc.txt"
writeFile file "I am just experimenting here."
readFile file
Tutaj utworzyliśmy plik tekstowy o nazwie „abc.txt”. Następnie wstawiliśmy instrukcję do pliku za pomocą poleceniawriteFile. Wreszcie użyliśmy poleceniareadFilewydrukować zawartość pliku na konsoli. Nasz kod wygeneruje następujący wynik -
I am just experimenting here.
Wyjątki
Na exceptionmożna uznać za błąd w kodzie. Jest to sytuacja, w której kompilator nie otrzymuje oczekiwanego wyniku w czasie wykonywania. Jak każdy inny dobry język programowania, Haskell zapewnia sposób implementacji obsługi wyjątków.
Jeśli znasz Javę, być może znasz blok Try-Catch, w którym zwykle zgłaszamy błąd i łapiemy to samo w catchblok. W Haskell mamy również tę samą funkcję do wychwytywania błędów uruchomieniowych.
Definicja funkcji trywygląda jak "try :: Exception e => IO a -> IO (albo ea)". Spójrz na poniższy przykładowy kod. Pokazuje, jak można złapać wyjątek „Podziel przez zero”.
import Control.Exception
main = do
result <- try (evaluate (5 `div` 0)) :: IO (Either SomeException Int)
case result of
Left ex -> putStrLn $ "Caught exception: " ++ show ex
Right val -> putStrLn $ "The answer was: " ++ show val
W powyższym przykładzie użyliśmy wbudowanego try funkcja Control.Exceptionmoduł, dlatego wcześniej wychwytujemy wyjątek. Powyższy fragment kodu spowoduje wyświetlenie poniżej wyniku na ekranie.
Caught exception: divide by zero
Functorw Haskell jest rodzajem funkcjonalnej reprezentacji różnych typów, które można odwzorować. Jest to koncepcja wysokiego poziomu implementacji polimorfizmu. Według twórców Haskell, wszystkie typy, takie jak Lista, Mapa, Drzewo, itp. Są instancjami Haskell Functor.
ZA Functor jest klasą wbudowaną z definicją funkcji taką jak -
class Functor f where
fmap :: (a -> b) -> f a -> f b
Na podstawie tej definicji możemy wywnioskować, że plik Functor jest funkcją, która przyjmuje funkcję, powiedzmy, fmap()i zwraca inną funkcję. W powyższym przykładziefmap() jest uogólnioną reprezentacją funkcji map().
W poniższym przykładzie zobaczymy, jak działa Haskell Functor.
main = do
print(map (subtract 1) [2,4,8,16])
print(fmap (subtract 1) [2,4,8,16])
Tutaj użyliśmy obu map() i fmap()nad listą operacji odejmowania. Można zauważyć, że obie instrukcje dadzą ten sam wynik listy zawierającej elementy [1,3,7,15].
Obie funkcje nazywały się inną funkcją o nazwie subtract() aby dać wynik.
[1,3,7,15]
[1,3,7,15]
Jaka jest różnica między map i fmap? Różnica polega na ich zastosowaniu. Functor pozwala nam zaimplementować więcej funkcjonalistów w różnych typach danych, takich jak „tylko” i „nic”.
main = do
print (fmap (+7)(Just 10))
print (fmap (+7) Nothing)
Powyższy fragment kodu przyniesie następujący wynik na terminalu -
Just 17
Nothing
Funktor aplikacyjny
Funktor aplikacyjny to normalny funktor z kilkoma dodatkowymi cechami zapewnianymi przez klasę typów aplikacyjnych.
Używając Functora, zwykle mapujemy istniejącą funkcję na inną zdefiniowaną w niej funkcję. Ale nie ma sposobu, aby odwzorować funkcję zdefiniowaną wewnątrz Functora z innym Functorem. Dlatego mamy inny obiekt o nazwieApplicative Functor. Ta możliwość mapowania jest implementowana przez klasę Applicative Type zdefiniowaną wControlmoduł. Ta klasa daje nam tylko dwie metody do pracy: jedna topure a drugi jest <*>.
Poniżej znajduje się definicja klasy Applicative Functor.
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Zgodnie z implementacją możemy zmapować innego Functora na dwa sposoby: "Pure" i "<*>". Metoda „Pure” powinna przyjmować wartość dowolnego typu i zawsze zwraca Applicative Functor o tej wartości.
Poniższy przykład pokazuje, jak działa Applicative Functor -
import Control.Applicative
f1:: Int -> Int -> Int
f1 x y = 2*x+y
main = do
print(show $ f1 <$> (Just 1) <*> (Just 2) )
Tutaj zaimplementowaliśmy funktory aplikacyjne w wywołaniu funkcji f1. Nasz program da następujący wynik.
"Just 4"
Monoidy
Wszyscy wiemy, że Haskell definiuje wszystko w postaci funkcji. W funkcjach mamy opcje, aby uzyskać nasze dane wejściowe jako dane wyjściowe funkcji. To właśnie jestMonoid jest.
ZA Monoidto zbiór funkcji i operatorów, których dane wyjściowe są niezależne od danych wejściowych. Weźmy funkcję (*) i liczbę całkowitą (1). Teraz, cokolwiek może być wejściem, jego wyjście pozostanie tylko tą samą liczbą. Oznacza to, że jeśli pomnożysz liczbę przez 1, otrzymasz tę samą liczbę.
Oto definicja monoidu w klasie typu.
class Monoid m where
mempty :: m
mappend :: m -> m -> m
mconcat :: [m] -> m
mconcat = foldr mappend mempty
Spójrz na poniższy przykład, aby zrozumieć użycie Monoid w Haskell.
multi:: Int->Int
multi x = x * 1
add :: Int->Int
add x = x + 0
main = do
print(multi 9)
print (add 7)
Nasz kod wygeneruje następujący wynik -
9
7
W tym przypadku funkcja „multi” mnoży wejście przez „1”. Podobnie funkcja „add” dodaje wejście z „0”. W obu przypadkach dane wyjściowe będą takie same jak dane wejściowe. Stąd funkcje{(*),1} i {(+),0} to doskonałe przykłady monoidów.
Monadssą niczym innym jak rodzajem funktorów aplikacyjnych z kilkoma dodatkowymi funkcjami. Jest to klasa typu, która rządzi trzema podstawowymi regułami znanymi jakomonadic rules.
Wszystkie trzy zasady mają ścisłe zastosowanie do deklaracji Monad, która jest następująca:
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
x >> y = x >>= \_ -> y
fail :: String -> m a
fail msg = error msg
Trzy podstawowe prawa, które mają zastosowanie do deklaracji Monad, to:
Left Identity Law - The returnfunkcja nie zmienia wartości i nie powinna niczego zmieniać w Monadzie. Można to wyrazić jako „return> => mf = mf”.
Right Identity Law - The returnfunkcja nie zmienia wartości i nie powinna niczego zmieniać w Monadzie. Można to wyrazić jako „mf> => return = mf”.
Associativity- Zgodnie z tym prawem zarówno instancja Functors, jak i Monad powinny działać w ten sam sposób. Można go matematycznie wyrazić jako „(f> ==> g)> => h = f> => (g> = h)”.
Pierwsze dwa prawa powtarzają ten sam punkt, tj. A return powinny mieć zachowanie tożsamości po obu stronach bind operator.
Użyliśmy już wielu monad w naszych poprzednich przykładach, nie zdając sobie sprawy, że są one monadami. Rozważmy następujący przykład, w którym używamy List Monad do wygenerowania określonej listy.
main = do
print([1..10] >>= (\x -> if odd x then [x*2] else []))
Ten kod wygeneruje następujące dane wyjściowe -
[2,6,10,14,18]
Zippers w Haskell są w zasadzie wskaźnikami wskazującymi na określoną lokalizację struktury danych, takiej jak plik tree.
Rozważmy a tree posiadające 5 elementów [45,7,55,120,56]które można przedstawić jako doskonałe drzewo binarne. Jeśli chcę zaktualizować ostatni element tej listy, muszę przejść przez wszystkie elementy, aby dotrzeć do ostatniego elementu, zanim go zaktualizuję. Dobrze?
Ale co by było, gdybyśmy mogli zbudować nasze drzewo w taki sposób, że drzewo posiadania N elementy to zbiór [(N-1),N]. Wtedy nie musimy przechodzić przez wszystkie niechciane(N-1)elementy. Możemy bezpośrednio zaktualizować N-ty element. To jest dokładnie koncepcja Zippera. Skupia się lub wskazuje na określone położenie drzewa, w którym możemy zaktualizować tę wartość bez przechodzenia przez całe drzewo.
W poniższym przykładzie zaimplementowaliśmy koncepcję Zippera na liście. W ten sam sposób można zaimplementować Zipper w plikutree lub a file struktura danych.
data List a = Empty | Cons a (List a) deriving (Show, Read, Eq, Ord)
type Zipper_List a = ([a],[a])
go_Forward :: Zipper_List a -> Zipper_List a
go_Forward (x:xs, bs) = (xs, x:bs)
go_Back :: Zipper_List a -> Zipper_List a
go_Back (xs, b:bs) = (b:xs, bs)
main = do
let list_Ex = [1,2,3,4]
print(go_Forward (list_Ex,[]))
print(go_Back([4],[3,2,1]))
Kiedy kompilujesz i uruchamiasz powyższy program, wygeneruje on następujące dane wyjściowe -
([2,3,4],[1])
([3,4],[2,1])
Tutaj skupiamy się na elemencie całej struny podczas jazdy do przodu lub do tyłu.