Typy, klasy typów, składnie w funkcji
Typy w Haskell Każde wyrażenie w Haskell posiada zdefiniowany typ. Dzięki temu już na etapie kompilacji kodu następuje sprawdzenie poprawności kodu i zabezpiecza nas przed ewentualnymi problemami z działaniem aplikacji w trakcie jej wykonywania.
Typy w Haskell W języku Haskelltypy są oznaczane przez nazwy rozpoczynające się od wielkiej litery. Np. Char, Bool, etc. Do sprawdzenia typu dowolnego wyrażenia w Haskell używamy polecenia :t :
Rozpoznawanie typu To czym Haskellwyróżnia się na tle innych języków to rozbudowana funkcjonalność rozpoznawania typów. Ta cecha pozwala na dużą swobodę w programowaniu ponieważ nie musimy każdorazowo jawnie wskazywać na konkretny typ żeby program zadziałał prawidłowo. Kompilator potrafi sam określić z jakim typem ma do czynienia.
Typy funkcji Ponieważ wszystkie wyrażenia w Haskell mają zdefiniowane typ także funkcje posiadają typy. Przy tworzeniu prostych funkcji nie ma potrzeby definiowania jawnego typów ponieważ jak już wspominaliśmy Haskellposiada rozbudowaną funkcjonalność rozpoznawania typów.
Funkcja bez definicji typu Rozważmy prostą funkcję pisz której zadaniem jest dopisanie do podanego wyrazu znaku wykrzyknika. Nasza funkcja będzie miała postać : pisz x = x++ "!" Wynikiem działania tej funkcji będzie :
Funkcja bez definicji typu Sprawdźmy jaki jest typ naszej funkcji pisz : Jak widać nasz funkcja posiada typ [Char] -> [Char] co oznacza mapuje typ znakowy na typ znakowy w związku z tym, że funkcja na wejściu bierze parametr typu znakowego i zwraca także parametr typu znakowego. [] w typie świadczy o tym, że mamy do czynienia z listą w tym przypadku listą znaków typu Char.
Funkcja z jawną definicją typu Ponieważ typ [Char] jest równoważny typowi String ponieważ Stringto nic innego jak lista pojedynczych znaków typu Char spróbujmy to wykorzystać to pokazania różnicy w przypadku jawnej deklaracji typu funkcji. Rozważmy funkcję pisz2 której zadaniem jest dopisanie do podanego wyrazu znaku wykrzyknika. Nasza funkcja będzie miała postać : pisz2 :: String -> String pisz2 x = x ++ "!"
Funkcja z jawną definicją typu Przy definiowaniu typów dla parametrów funkcji posługujemy się symbolem ->. Typ wartość zwracanej znajduje się na końcu definiowanych typów. Po uruchomieniu funkcji pisz2 i sprawdzeniu jej typu możemy zaobserwować, że działanie funkcji pisz2 jest identyczne jak funkcji pisz mimo iż funkcja pisz2 jest typu [String] -> [String].
Typy zmienne W Haskell mamy także dyspozycji zmienne typy czyli taką konstrukcję która nie narzuca z góry określonego typu dla parametru. Rozważmy wbudowaną funkcję head która zwraca pierwszy obiekt z listy :
Typy zmienne Jak widzimy funkcja head nie ma określonego konkretnego typu. Funkcja ta na wejściu przyjmuje listę nieokreślonego typu i zwraca pierwszy obiekt tej listy o nieokreślonym typie. Funkcje które przyjmują typy zmienny nazywamy funkcjami polimorficznymi.
Typeclasses Klasy typów Klasy typów w Haskellsą pewnego rodzaju mechanizmem określającym sposób zachowania poszczególnych typów związanych z daną klasą. Rozważmy typ funkcji porównania == : Symbol => nazwany jest w Haskellclassconstraint, czyli ograniczeniem klasy.
Typeclasses Klasy typów Tak zdeklarowany typ funkcji == oznacza, że funkcja porównania pobiera na wejściu dwie wartości tego samego typu i zwraca wartość typu Bool, gdzie obie wartości wejściowe muszą należeć do klasy typów Eq.
Podstawowe klasy typów Eq dla typów używanych w porównaniach ( ==,/= ) Ord dla typów używanych w porządkowaniu wartości ( <, >, <=, >= ) Show dla typów które mogę prezentowane jako String Num dla wszystkich typów numerycznych Integral dla typów liczb całkowitych Floating dla typów liczb zmiennoprzecinkowych Enum dla typów wyliczalnych
Składanie w funkcji Poniżej opiszemy kilka podstawowych konstrukcji składni w funkcjach dostępnych w Haskell.
Porównywanie wzorca Składnia daje możliwość tworzenia konstrukcji pozwalających na wskazywanie konkretnych wzorców, porównywanie ich z parametrami wejściowymi funkcji która posiada zaimplementowaną taką funkcjonalność ( matching ) i weryfikacji danych wejściowych względem zaimplementowanego wzorca ( data deconstructing). Składnia takiej funkcji pozwala na implementację różnych zachowań funkcji w zależności od wykorzystywanego wzorca.
Porównywanie wzorca Rozważmy konstrukcję prostej funkcji sprawdzającej czy wartością na wejściu jest liczba 1 lub 2 : checknum:: (Integrala) => a-> String checknum1 = "To jest liczba jeden" checknum2 = "To jest liczba dwa" checknumx = "To nie jest 1 lub 2"
Porównywanie wzorca Warto zaznaczyć, że weryfikacja wzorca następuje od góry do dołu ciała funkcji. Jeśli weryfikowana wartość ( w tym przypadku 1 ) pasuje do pierwszego wzorca określonego w funkcji do sprawdzenie zatrzyma się na pierwszym wzorcu i reszta nie będzie weryfikowana mimo iż teoretycznie wartość 1 pasuje także do wzorca ostatniego. Oznacza to, że gdybyśmy wzorzec checknumx = "To nie jest 1 lub 2" umieścili jako pierwszy od góry w naszej funkcji to każda liczba podana jako parametr wejściowy wyświetli To nie jest 1 lub 2.
Porównywanie wzorca Działanie funkcji checknum będzie następujące :
Porównywanie wzorca Istotnym jest aby przy budowaniu tego typu funkcji pamiętać o umieszczaniu na końcu wzorca który odpowiada za wszystkie pozostałe przypadki czyli w przypadku naszej funkcji checknumx = "To nie jest 1 lub 2". Jest to istotne zabezpieczenie naszej funkcji przed niekontrolowanym błędem wykonania.
Strażnicy Strażnik jest w Haskell mechanizmem w sowim działaniu bardzo zbliżonym do warunku if. Wynikiem działania strażników jest wartość logiczna czyli True lub False. Mimo iż funkcjonalnie strażnicy są bardzo podobni do konstrukcji if to ich implementacja zdecydowanie zwiększa czytelność kodu. Dodatkowo strażnicy bardzo dobrze współpracują z wcześniej opisanym mechanizmem porównywania wzorców.
Strażnicy Rozważmy prostą funkcję z wykorzystaniem strażników której zadaniem będzie określenie etapu życia człowieka w zależności od podanego wieku jako parametru wejściowego : checkage:: (Integrala) => a-> String checkage age age <= 18 = "Natsolatek" age<= 65 = "Dorosly" age<= 110 = "Emeryt" otherwise = "Trudno powiedziec"
Strażnicy Zauważmy, że implementacja strażnika polega na użyciu symboli następnie podaniu warunku dla strażnika oraz po znaku = podaniu wartości wyrażenia które ma zaistnieć po spełnieniu tego warunku. Tutaj podobnie jak w porównywaniu wzorców istotna jest kolejność strażników oraz zadbaniu aby na końcu takiej funkcji zawsze znajdował się warunek odpowiadający wszystkim innym możliwością od zaimplementowanych. W tym celu przy wykorzystaniu strażników posługujemy się wyrażeniem otherwise.
Strażnicy Działanie funkcji checkage będzie następujące :
Strażnicy Możemy także zastosować w naszej funkcji więcej niż jeden parametr. Rozważmy poniższą funkcję do weryfikacji BMI : bmitell weight height weight/ height^ 2 <= 18.5 = " Jestesza chudy" weight/ height^ 2 <= 25.0 = " Jest ok" weight/ height^ 2 <= 30.0 = " Trocheza duzocialka" otherwise= " Dzwon do lekarza"
Strażnicy Działanie naszej funkcji sprawdzającej indeks BMI :
Where Na przykładzie funkcji z dwoma parametrami do sprawdzania indeksu BMI można zauważyć, że powtarzamy pewną częścfunkcji a mianowicie fragment weight/ height^ 2. Takie wielokrotne powtarzanie tego samego nie jest oczekiwanym zachowaniem w programowaniu. W celu uniknięcia takich powtórzeń możemy zastosować w naszej składni wyrażenie Where nazywane w także w Haskell wiązaniem.
Where Rozważmy zmodyfikowaną o użycie wyrażenia where funkcję do sprawdzania indeksu BMI : bmitell2 :: ( RealFloata) => a-> a-> String bmitell2 weight height bmi <= 18.5 = " Jestesza chudy" bmi <= 25.0 = " Jest ok" bmi <= 30.0 = " Trocheza duzocialka" otherwise= " Dzwon do lekarza" wherebmi = weight/ height^ 2
Where Warunek where umieszczamy na końcu naszej listy zdefiniowanych strażników. Dzięki wykorzystaniu tego wyrażenia nasz kod staje się dużo bardziej czytelny przy zachowaniu tej samej funkcjonalności :
Let Wyrażenie let w kontekście wykorzystania w funkcjach ze strażnikami jest w swoim działaniu bardzo podobne do wyrażenia where i także jest określany w języku Haskell mianem wiązania. Różnica między let a where polega na tym, że let działa bardziej lokalnie, tj. nie obejmuje zakresem swojego działania całego strażnika. W skrócie działania wyrażenia let polega na przypisaniu pewnych wartości do podanych zmiennych.
Let