Szukaj…


Wprowadzenie

Klasy typów w Haskell są sposobem na zdefiniowanie zachowania związanego z typem oddzielnie od definicji tego typu. Podczas gdy, powiedzmy, w Javie, zdefiniujesz zachowanie jako część definicji typu - tj. W interfejsie, klasie abstrakcyjnej lub konkretnej - Haskell zachowuje te dwie rzeczy osobno.

W pakiecie base Haskell zdefiniowano już kilka typów czcionek. Zależność między nimi zilustrowano w sekcji Uwagi poniżej.

Uwagi

Poniższy schemat zaczerpnięty z artykułu Typeclassopedia pokazuje związek między różnymi typami klas w Haskell.

Relacje między standardowymi klasami typu Haskell, ryc. 1 opublikowane w Typeclassopedia.

Może i klasa Functor

W Haskell typy danych mogą mieć argumenty podobnie jak funkcje. Weźmy na przykład typ Maybe .

Maybe jest to bardzo przydatny typ, który pozwala nam przedstawić ideę niepowodzenia lub jego możliwość. Innymi słowy, jeśli istnieje możliwość, że obliczenia nie powiodą się, używamy tam typu Maybe . Maybe działa jak opakowanie dla innych typów, dając im dodatkową funkcjonalność.

Jego rzeczywista deklaracja jest dość prosta.

Maybe a = Just a | Nothing

Mówi to, że Maybe występuje w dwóch formach: Just , który reprezentuje sukces, i Nothing , który reprezentuje porażkę. Just wziąć jeden argument, który określa typ Maybe , i Nothing bierze żadnego. Na przykład wartość Just "foo" będzie miała typ Maybe String , który jest typem łańcucha zapakowanym z dodatkową funkcją Maybe . Wartość Nothing ma typu Maybe a gdzie a może być dowolnego typu.

Pomysł zawijania typów w celu zapewnienia im dodatkowej funkcjonalności jest bardzo przydatny i ma zastosowanie nie tylko do Maybe . Inne przykłady obejmują Either , IO i typy list, z których każdy zapewnia inną funkcjonalność. Istnieją jednak pewne działania i zdolności, które są wspólne dla wszystkich tych rodzajów opakowań. Najważniejszą z nich jest możliwość modyfikacji wartości kapsułkowanej.

Często uważa się tego rodzaju typy za pola, w których mogą znajdować się wartości. Różne pola przechowują różne wartości i wykonują różne czynności, ale żadne z nich nie jest przydatne bez możliwości uzyskania dostępu do zawartości.

Aby zawrzeć ten pomysł, Haskell jest dostarczany ze standardową czcionką o nazwie Functor . Jest zdefiniowany w następujący sposób.

class Functor f where
  fmap :: (a -> b) -> f a -> f b

Jak widać, klasa ma jedną funkcję, fmap , fmap z dwóch argumentów. Pierwszy argument jest funkcją jednego typu, a , drugiego, b . Drugi argument to funktor (typ opakowania) zawierający wartość typu a . Zwraca funktor (typ opakowania) zawierający wartość typu b .

Mówiąc fmap , fmap przyjmuje funkcję i stosuje się do wartości wewnątrz funktora. Jest to jedyna funkcja niezbędna, aby typ był członkiem klasy Functor , ale jest to niezwykle przydatne. Funkcje działające na funktorach, które mają bardziej szczegółowe zastosowania, można znaleźć w typach klas Applicative i Monad .

Dziedziczenie klasy typu: klasa typu Ord

Haskell popiera pojęcie rozszerzenia klasy. Na przykład klasa Ord dziedziczy wszystkie operacje w Eq , ale dodatkowo ma funkcję compare która zwraca Ordering między wartościami. Ord mogą również zawierać typowe operatorów celu porównania, jak również min sposobu i max metody.

Notacja => ma takie samo znaczenie jak w sygnaturze funkcji i wymaga wpisania a aby zaimplementować Eq , aby zaimplementować Ord .

data Ordering = EQ | LT | GT

class Eq a => Ord a where
    compare :: Ord a => a -> a -> Ordering
    (<)     :: Ord a => a -> a -> Bool
    (<=)    :: Ord a => a -> a -> Bool
    (>)     :: Ord a => a -> a -> Bool
    (>=)    :: Ord a => a -> a -> Bool
    min     :: Ord a => a -> a -> a
    max     :: Ord a => a -> a -> a

Wszystkie poniższe metody compare można z niego wyprowadzić na wiele sposobów:

x < y   = compare x y == LT
x <= y  = x < y || x == y -- Note the use of (==) inherited from Eq
x > y   = not (x <= y)
x >= y  = not (x < y)

min x y = case compare x y of
               EQ -> x
               LT -> x
               GT -> y

max x y = case compare x y of
               EQ -> x
               LT -> y
               GT -> x

Klasy typów, które same rozszerzają Ord muszą implementować przynajmniej metodę compare lub samą metodę (<=) , która tworzy ukierunkowaną sieć dziedziczenia.

Równ

Wszystkie podstawowe typy danych (takie jak Int , String , Eq a => [a] ) z Prelude, z wyjątkiem funkcji i IO mają instancje Eq . Jeśli typ tworzy Eq , oznacza to, że wiemy, jak porównać dwie wartości pod względem wartości lub równości strukturalnej .

> 3 == 2 
False
> 3 == 3
True

Wymagane metody

  • (==) :: Eq a => a -> a -> Boolean lub (/=) :: Eq a => a -> a -> Boolean (jeśli tylko jeden jest zaimplementowany, drugi domyślnie neguje zdefiniowany)

Definiuje

  • (==) :: Eq a => a -> a -> Boolean
  • (/=) :: Eq a => a -> a -> Boolean

Bezpośrednie nadklasy

Żaden

Godne uwagi podklasy

Ord

Typy tworzące Ord obejmują, np. Int , String i [a] (dla typów, a których występuje Ord a instancja). Jeśli typ tworzy instancję Ord , oznacza to, że znamy „naturalne” uporządkowanie wartości tego typu. Zauważ, że często istnieje wiele możliwych wyborów „naturalnego” uporządkowania typu i Ord zmusza nas do faworyzowania jednego z nich.

Ord udostępnia standardowe operatory (<=) , (<) , (>) , (>=) , ale co ciekawe, definiuje je wszystkie za pomocą niestandardowego typu danych algebraicznych

data Ordering = LT | EQ | GT

compare :: Ord a => a -> a -> Ordering

Wymagane metody

  • compare :: Ord a => a -> a -> Ordering lub (<=) :: Ord a => a -> a -> Boolean (domyślna metoda compare standardu używa (<=) w swojej implementacji)

Definiuje

  • compare :: Ord a => a -> a -> Ordering
  • (<=) :: Ord a => a -> a -> Boolean
  • (<) :: Ord a => a -> a -> Boolean
  • (>=) :: Ord a => a -> a -> Boolean
  • (>) :: Ord a => a -> a -> Boolean
  • min :: Ord a => a -> a -> a
  • max :: Ord a => a -> a -> a

Bezpośrednie nadklasy

Monoid

Typy tworzące Monoid obejmują między innymi listy, liczby i funkcje z zwracanymi wartościami Monoid . Aby utworzyć instancję Monoid typ musi obsługiwać asocjacyjną operację binarną ( mappend lub (<>) ), która łączy jego wartości, i ma specjalną wartość „zero” ( mempty ), tak że połączenie z nią wartości nie zmienia tej wartości:

mempty  <>  x == x
x <>  mempty  == x

x <> (y <> z) == (x <> y) <> z

Intuicyjnie typy Monoid są „podobne do list”, ponieważ obsługują łączenie wartości razem. Alternatywnie, typy Monoid można traktować jako sekwencje wartości, dla których zależy nam na porządku, ale nie na grupowaniu. Na przykład, drzewo binarne jest Monoid , ale za pomocą operacji Monoid nie możemy obserwować jego rozgałęzionej struktury, a jedynie przechodzenie przez jego wartości (patrz Foldable i Traversable ).

Wymagane metody

  • mempty :: Monoid m => m
  • mappend :: Monoid m => m -> m -> m

Bezpośrednie nadklasy

Żaden

Num

Najbardziej ogólna klasa dla typów liczb, a dokładniej dla pierścieni , tzn. Liczb, które można dodawać, odejmować i mnożyć w zwykłym znaczeniu, ale niekoniecznie dzielą się.

Ta klasa zawiera zarówno typy całkowe ( Int , Integer , Word32 itp.), Jak i typy ułamkowe ( Double , Rational , również liczby zespolone itp.). W przypadku typów skończonych semantykę rozumie się ogólnie jako arytmetykę modułową , tj. Z przepełnieniem i niedopełnieniem .

Zauważ, że reguły dla klas numerycznych są znacznie mniej ściśle przestrzegane niż prawa monady lub prawa monoidów lub te dla porównania równości . W szczególności liczby zmiennoprzecinkowe są zasadniczo zgodne z prawami tylko w przybliżeniu.

Metody

  • fromInteger :: Num a => Integer -> a . przekonwertować liczbę całkowitą na ogólny typ liczb (w razie potrzeby zawijając wokół zakresu). Literały liczbowe Haskella można rozumieć jako monomorficzny literał Integer z ogólną konwersją wokół niego, dzięki czemu można używać literału 5 zarówno w kontekście Int i Complex Double ustawieniu Complex Double .

  • (+) :: Num a => a -> a -> a . Standardowe dodawanie, ogólnie rozumiane jako asocjacyjne i przemienne, tj.

      a + (b + c) ≡ (a + b) + c
      a + b ≡ b + a
    
  • (-) :: Num a => a -> a -> a . Odejmowanie, które jest odwrotnością dodawania:

      (a - b) + b ≡ (a + b) - b ≡ a
    
  • (*) :: Num a => a -> a -> a . Mnożenie, operacja asocjacyjna, która rozdziela po dodaniu:

      a * (b * c) ≡ (a * b) * c
      a * (b + c) ≡ a * b + a * c
    

    w najczęstszych przypadkach mnożenie jest również przemienne, ale z pewnością nie jest to wymóg.

  • negate :: Num a => a -> a . Pełna nazwa jednoargumentowego operatora negacji. -1 to cukier składniowy dla negate 1 .

      -a ≡ negate a ≡ 0 - a
    
  • abs :: Num a => a -> a . Funkcja wartości bezwzględnej zawsze daje wynik nieujemny o tej samej wielkości

      abs (-a) ≡ abs a
      abs (abs a) ≡ abs a
    

    abs a ≡ 0 powinno się zdarzyć tylko wtedy, a ≡ 0 .

    W przypadku typów rzeczywistych jasne jest, co oznacza wartość nieujemna: zawsze masz abs a >= 0 . Złożone typy itp. Nie mają dobrze zdefiniowanego uporządkowania, jednak wynik abs powinien zawsze leżeć w rzeczywistym podzbiorze (tzn. Podać liczbę, która może być również zapisana jako literał liczby pojedynczej bez negacji).

  • signum :: Num a => a -> a . Funkcja znak, zgodnie z nazwą, daje tylko -1 lub 1 , w zależności od znaku argumentu. W rzeczywistości dotyczy to tylko niezerowych liczb rzeczywistych; ogólnie signum jest lepiej rozumiane jako funkcja normalizująca :

      abs (signum a) ≡ 1   -- unless a≡0
      signum a * abs a ≡ a -- This is required to be true for all Num instances
    

    Zauważ, że sekcja 6.4.4 raportu Haskell 2010 wyraźnie wymaga, aby ta ostatnia równość była ważna dla każdej ważnej instancji Num .


Niektóre biblioteki, zwłaszcza liniowe i hmatrixowe , mają znacznie bardziej swobodne rozumienie, do czego służy klasa Num : traktują ją jako sposób na przeciążenie operatorów arytmetycznych . Chociaż jest to dość proste dla + i - , już staje się kłopotliwe z * a bardziej z innymi metodami. Na przykład, czy * powinno oznaczać mnożenie macierzy czy mnożenie elementów?
Prawdopodobnie złym pomysłem jest zdefiniowanie takich instancji niepoliczalnych; proszę rozważyć dedykowane klasy, takie jak VectorSpace .


W szczególności „negatywy” niepodpisanych typów są (-4 :: Word32) == 4294967292 dużymi dodatnimi, np. (-4 :: Word32) == 4294967292 .

ta nie jest powszechnie spełniony: typy wektorowe nie mają realną podzbiór. Kontrowersyjne Num substancje dla takich typów ogólnie definiują abs i signum elementarnie, co matematycznie nie ma sensu.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow