Haskell Language
Type klassen
Zoeken…
Invoering
Typeklassen in Haskell zijn een manier om het gedrag dat aan een type is gekoppeld, los van de definitie van dat type te definiëren. Terwijl je bijvoorbeeld in Java het gedrag zou definiëren als onderdeel van de definitie van het type - dat wil zeggen in een interface, abstracte klasse of concrete klasse - houdt Haskell deze twee dingen gescheiden.
Er zijn een aantal typeclasses reeds in Haskell gedefinieerd base
pakket. De relatie hiertussen wordt geïllustreerd in het gedeelte Opmerkingen hieronder.
Opmerkingen
Het volgende diagram uit het artikel Typeclassopedia toont de relatie tussen de verschillende typeclasses in Haskell.
Misschien en de Functor Class
In Haskell kunnen gegevenstypen argumenten hebben, net als functies. Neem bijvoorbeeld het type Maybe
.
Maybe
is het een zeer nuttig type waarmee we het idee van mislukking of de mogelijkheid daarvan kunnen weergeven. Met andere woorden, als er een mogelijkheid is dat een berekening mislukt, gebruiken we daar het type Maybe
. Maybe
gedraagt het zich een beetje als een wrapper voor andere typen, waardoor ze extra functionaliteit krijgen.
De feitelijke verklaring is vrij eenvoudig.
Maybe a = Just a | Nothing
Wat dit vertelt, is dat een Maybe
in twee vormen komt, een Just
, die succes vertegenwoordigt, en een Nothing
, dat mislukking vertegenwoordigt. Just
één argument dat het type van de Maybe
bepaalt, en Nothing
neemt er geen. De waarde Just "foo"
heeft bijvoorbeeld het type Maybe String
, wat een tekenreekstype is dat is verpakt met de extra Maybe
functionaliteit. De waarde Nothing
heeft type Maybe a
waar a
elk type kan zijn.
Dit idee van verpakkingstypes om ze extra functionaliteit te geven, is erg handig en is van toepassing op meer dan alleen Maybe
. Andere voorbeelden zijn de Either
, IO
en lijsttypen, die allemaal verschillende functionaliteit. Er zijn echter een aantal acties en mogelijkheden die voor al deze soorten omslagen gelden. De meest opvallende hiervan is de mogelijkheid om de ingekapselde waarde te wijzigen.
Het is gebruikelijk om dit soort typen te beschouwen als vakken waarin waarden kunnen worden geplaatst. Verschillende vakken bevatten verschillende waarden en doen verschillende dingen, maar geen enkele is nuttig zonder toegang te hebben tot de inhoud erin.
Om dit idee samen te vatten, wordt Haskell geleverd met een standaardtypeklasse, Functor
genaamd. Het is als volgt gedefinieerd.
class Functor f where
fmap :: (a -> b) -> f a -> f b
Zoals te zien is, heeft de klasse een enkele functie, fmap
, van twee argumenten. Het eerste argument is een functie van het ene type, a
, naar het andere, b
. Het tweede argument is een functor (wrapper-type) met een waarde van type a
. Het retourneert een functor (wrapper-type) met een waarde van type b
.
In eenvoudige bewoordingen neemt fmap
een functie aan en is deze van toepassing op de waarde binnen een functor. Het is de enige functie die nodig is voor een type om lid te zijn van de Functor
klasse, maar het is uiterst nuttig. Functies die werken op functoren die meer specifieke toepassingen hebben, zijn te vinden in de Applicative
en Monad
typeclasses.
Overerving type klasse: klasse Ord-type
Haskell ondersteunt een notie van klasse-extensie. De klasse Ord
neemt bijvoorbeeld alle bewerkingen in Eq
, maar heeft bovendien een compare
die een Ordering
tussen waarden retourneert. Ord
kan ook de algemene operatoren voor ordervergelijking bevatten, evenals een min
methode en een max
methode.
De notatie =>
heeft dezelfde betekenis als in een functiehandtekening en vereist type a
om Eq
te implementeren, om Ord
te implementeren.
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
Alle methoden volgende compare
kan worden afgeleid op verschillende manieren:
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
Typeklassen die zelf Ord
uitbreiden, moeten ten minste de compare
of de methode (<=)
zelf implementeren, waarmee het gerichte overervingsrooster wordt opgebouwd.
Eq
Alle basisdatatypes (zoals Int
, String
, Eq a => [a]
) van Prelude behalve functies en IO
hebben instanties van Eq
. Als een type Eq
instantieert, betekent dit dat we weten hoe we twee waarden kunnen vergelijken voor waarde of structurele gelijkheid.
> 3 == 2
False
> 3 == 3
True
Vereiste methoden
-
(==) :: Eq a => a -> a -> Boolean
of(/=) :: Eq a => a -> a -> Boolean
(als er maar één is geïmplementeerd, gebruikt de andere standaard de ontkenning van de gedefinieerde)
definieert
-
(==) :: Eq a => a -> a -> Boolean
-
(/=) :: Eq a => a -> a -> Boolean
Directe superklassen
Geen
Opmerkelijke subklassen
Ord
Typen die Ord
instantiëren, zijn bijvoorbeeld Int
, String
en [a]
(voor typen a
waar een Ord a
instantie is). Als een type Ord
instantieert, betekent dit dat we een 'natuurlijke' ordening van waarden van dat type kennen. Let op, er zijn vaak veel mogelijke keuzes van de "natuurlijke" ordening van een type en Ord
dwingt ons om er een te kiezen.
Ord
biedt de standaard (<=)
, (<)
, (>)
, (>=)
operatoren, maar interessant definieert ze allemaal met een aangepast algebraïsch gegevenstype
data Ordering = LT | EQ | GT
compare :: Ord a => a -> a -> Ordering
Vereiste methoden
-
compare :: Ord a => a -> a -> Ordering
of(<=) :: Ord a => a -> a -> Boolean
(de standaardcompare
de standaard gebruikt(<=)
in zijn implementatie)
definieert
-
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
Directe superklassen
Monoid
Typen die Monoid
instantiëren, zijn Monoid
andere lijsten, getallen en functies met Monoid
retourwaarden. Om Monoid
te instantiëren, Monoid
een type een associatieve binaire bewerking ( mappend
of (<>)
) ondersteunen die zijn waarden combineert, en een speciale "nul" -waarde ( mempty
) hebben zodat het combineren van een waarde deze waarde niet verandert:
mempty <> x == x
x <> mempty == x
x <> (y <> z) == (x <> y) <> z
Intuïtief zijn Monoid
typen " Monoid
" in die zin dat ze samen toegevoegde waarden ondersteunen. Als alternatief kunnen Monoid
typen worden beschouwd als reeksen waarden waarvoor we de volgorde belangrijk vinden, maar niet de groepering. Een binaire boom is bijvoorbeeld een Monoid
, maar met behulp van de Monoid
bewerkingen zijn we niet getuige van de vertakkende structuur, alleen een transversatie van de waarden (zie Foldable
en Traversable
).
Vereiste methoden
-
mempty :: Monoid m => m
-
mappend :: Monoid m => m -> m -> m
Directe superklassen
Geen
Num
De meest algemene klasse voor nummertypen, meer bepaald voor ringen , dwz nummers die kunnen worden opgeteld en afgetrokken en vermenigvuldigd in de gebruikelijke zin, maar niet noodzakelijkerwijs verdeeld.
Deze klasse bevat zowel integrale typen ( Int
, Integer
, Word32
enz.) Als fractionele typen ( Double
, Rational
, ook complexe getallen enz.). In het geval van eindige typen, wordt de semantiek in het algemeen begrepen als modulaire rekenkunde , dwz met over- en onderstroom † .
Merk op dat de regels voor de numerieke klassen veel minder strikt worden nageleefd dan de monade of monoïde wetten, of die voor gelijkheidsvergelijking . In het bijzonder gehoorzamen getallen met drijvende komma meestal wetten slechts in een benaderde zin.
De methodes
fromInteger :: Num a => Integer -> a
. converteer een geheel getal naar het algemene nummertype (indien nodig om het bereik heen). Haskell- aantalliteralen kunnen worden opgevat als een monomorfeInteger
letterlijke letter met de algemene conversie eromheen, dus u kunt de letterlijke5
zowel in eenInt
context als in eenComplex Double
instelling gebruiken.(+) :: Num a => a -> a -> a
. Standaard toevoeging, algemeen opgevat als associatief en commutatief, dwza + (b + c) ≡ (a + b) + c a + b ≡ b + a
(-) :: Num a => a -> a -> a
. Aftrekken, wat het omgekeerde is van optellen:(a - b) + b ≡ (a + b) - b ≡ a
(*) :: Num a => a -> a -> a
. Vermenigvuldiging, een associatieve bewerking die verspreid is over toevoeging:a * (b * c) ≡ (a * b) * c a * (b + c) ≡ a * b + a * c
voor de meest voorkomende gevallen is vermenigvuldiging ook commutatief, maar dit is absoluut geen vereiste.
negate :: Num a => a -> a
. De volledige naam van de unaire negatie-operator.-1
is syntactische suiker voornegate 1
.-a ≡ negate a ≡ 0 - a
abs :: Num a => a -> a
. De functie absolute waarde geeft altijd een niet-negatief resultaat van dezelfde grootteabs (-a) ≡ abs a abs (abs a) ≡ abs a
abs a ≡ 0
mag alleen gebeuren alsa ≡ 0
.Voor echte types is het duidelijk wat niet-negatief betekent: je hebt altijd
abs a >= 0
. Complexe enz. Typen hebben geen goed gedefinieerde volgorde, maar het resultaat vanabs
moet altijd in de echte subset liggen ‡ (dat wil zeggen een getal geven dat ook zonder ontkenning als een enkel getal kan worden geschreven).signum :: Num a => a -> a
. De tekenfunctie levert volgens de naam slechts-1
of1
, afhankelijk van het teken van het argument. Dat geldt eigenlijk alleen voor niet-nul reële getallen; in het algemeen wordtsignum
beter begrepen als de normaliserende functie:abs (signum a) ≡ 1 -- unless a≡0 signum a * abs a ≡ a -- This is required to be true for all Num instances
Merk op dat paragraaf 6.4.4 van het Haskell 2010-rapport expliciet vereist dat deze laatste gelijkheid geldt voor elke geldige
Num
instantie.
Sommige bibliotheken, met name lineaire en hmatrix , begrijpen veel minder goed waar de Num
klasse voor is: ze behandelen het alleen als een manier om de rekenkundige operatoren te overbelasten . Hoewel dit vrij eenvoudig is voor +
en -
, wordt het al lastig met *
en meer met de andere methoden. Moet bijvoorbeeld *
matrixvermenigvuldiging of elementgewijze vermenigvuldiging betekenen?
Het is aantoonbaar een slecht idee om dergelijke niet-nummerinstanties te definiëren; overweeg speciale klassen zoals VectorSpace
.
† In het bijzonder worden de "negatieven" van niet-ondertekende typen omwikkeld tot groot positief, bijv. (-4 :: Word32) == 4294967292
.
‡ Dit wordt grotendeels niet vervuld: vectortypen hebben geen echte subset. De controversiële Num
instanties voor dergelijke typen definiëren in het algemeen abs
en signum
element-wijs, wat wiskundig gezien niet echt logisch is.