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.

Relaties tussen standaard Haskell-type klassen, figuur 1 zoals gepubliceerd in Typeclassopedia.

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 standaard compare 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 monomorfe Integer letterlijke letter met de algemene conversie eromheen, dus u kunt de letterlijke 5 zowel in een Int context als in een Complex Double instelling gebruiken.

  • (+) :: Num a => a -> a -> a . Standaard toevoeging, algemeen opgevat als associatief en commutatief, dwz

      a + (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 voor negate 1 .

      -a ≡ negate a ≡ 0 - a
    
  • abs :: Num a => a -> a . De functie absolute waarde geeft altijd een niet-negatief resultaat van dezelfde grootte

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

    abs a ≡ 0 mag alleen gebeuren als a ≡ 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 van abs 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 of 1 , afhankelijk van het teken van het argument. Dat geldt eigenlijk alleen voor niet-nul reële getallen; in het algemeen wordt signum 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.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow