Haskell Language
Typ klasser
Sök…
Introduktion
Typglasögon i Haskell är ett sätt att definiera beteendet förknippat med en typ separat från den typens definition. Medan säga, i Java, skulle du definiera beteendet som en del av typens definition - dvs i ett gränssnitt, abstrakt klass eller konkret klass - Haskell håller dessa två saker åtskilda.
Det finns ett antal typeclasses redan definierade i Haskell s base
paket. Förhållandet mellan dessa illustreras i avsnittet Kommentarer nedan.
Anmärkningar
Följande diagram som tagits från artikeln Typeclassopedia visar förhållandet mellan de olika typglasögonen i Haskell.
Kanske och Functor Class
I Haskell kan datatyper ha argument precis som funktioner. Ta till exempel Maybe
typen.
Maybe
är en mycket användbar typ som gör att vi kan representera idén om misslyckande eller deras möjlighet. Med andra ord, om det finns en möjlighet att en beräkning misslyckas använder vi typen Maybe
där. Maybe
fungerar som ett omslag för andra typer, vilket ger dem ytterligare funktionalitet.
Dess verkliga förklaring är ganska enkel.
Maybe a = Just a | Nothing
Vad detta berättar är att ett Maybe
finns i två former, en Just
, som representerar framgång och ett Nothing
, som representerar misslyckande. Just
ett argument som bestämmer typen av Maybe
, och Nothing
tar inget. Till exempel kommer värdet Just "foo"
att ha typen Maybe String
, som är en strängtyp som är lindad med den extra Maybe
funktionen. Värdet Nothing
har typ Maybe a
där a
kan vara vilken typ som helst.
Denna idé om inslagningstyper för att ge dem ytterligare funktionalitet är en mycket användbar idé och är tillämplig på mer än bara Maybe
. Andra exempel inkluderar Either
, Either
IO
och listtyper, var och en med olika funktioner. Det finns dock vissa åtgärder och förmågor som är gemensamma för alla dessa omslagstyper. Det mest anmärkningsvärda av dessa är förmågan att ändra det inkapslade värdet.
Det är vanligt att tänka på denna typ av typer som rutor som kan ha värden placerade i dem. Olika rutor har olika värden och gör olika saker, men ingen är användbar utan att kunna komma åt innehållet inom.
För att kapsla in denna idé kommer Haskell med ett standardtypklass, med namnet Functor
. Det definieras enligt följande.
class Functor f where
fmap :: (a -> b) -> f a -> f b
Som framgår har klassen en enda funktion, fmap
, av två argument. Det första argumentet är en funktion från en typ, a
, till en annan, b
. Det andra argumentet är en funktor (omslagstyp) som innehåller ett värde av typ a
. Den returnerar en funktor (omslagstyp) som innehåller ett värde av typ b
.
Enkelt fmap
tar fmap
en funktion och gäller värdet inuti en funktor. Det är den enda funktion som krävs för att en typ ska vara medlem i Functor
klassen, men den är oerhört användbar. Funktioner som fungerar på funktorer som har mer specifika applikationer kan hittas i Applicative
och Monad
typglasögon.
Typklassarv: Ord typklass
Haskell stöder en uppfattning om klassförlängning. Exempelvis ärver klassen Ord
alla operationer i Eq
, men har dessutom en compare
funktion som returnerar en Ordering
mellan värden. Ord
kan också innehålla operatörer för vanliga ordningsjämförelser, såväl som en min
metod och en max
metod.
Notationen =>
har samma betydelse som den gör i en funktionssignatur och kräver typ a
att implementera Eq
för att implementera 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
Alla metoder som följer compare
kan härledas från det på ett antal sätt:
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
Typklasser som själva utvidgar Ord
måste implementera åtminstone antingen compare
eller (<=)
metoden själva, som bygger upp det riktade arvgitteret.
ekv
Alla grundläggande datatyper (som Int
, String
, Eq a => [a]
) från Prelude förutom för funktioner och IO
har instanser av Eq
. Om en typ instanser Eq
betyder det att vi vet hur man kan jämföra två värden för värde eller strukturell jämlikhet.
> 3 == 2
False
> 3 == 3
True
Obligatoriska metoder
-
(==) :: Eq a => a -> a -> Boolean
eller(/=) :: Eq a => a -> a -> Boolean
(om bara en är implementerad, är den andra som standard att negationen av definierad en)
definierar
-
(==) :: Eq a => a -> a -> Boolean
-
(/=) :: Eq a => a -> a -> Boolean
Direkt superklass
Ingen
Anmärkningsvärda underklasser
Ord
Typer som instanterar Ord
inkluderar t.ex. Int
, String
och [a]
(för typer a
där det finns en Ord a
instans). Om en typ instanserar Ord
betyder det att vi känner till en "naturlig" ordning av värden av den typen. Observera att det ofta finns många möjliga val av den "naturliga" beställningen av en typ och Ord
tvingar oss att gynna en.
Ord
tillhandahåller operatörerna för standard (<=)
, (<)
, (>)
, (>=)
men definierar intressant dem alla med en anpassad algebraisk datatyp
data Ordering = LT | EQ | GT
compare :: Ord a => a -> a -> Ordering
Obligatoriska metoder
-
compare :: Ord a => a -> a -> Ordering
eller(<=) :: Ord a => a -> a -> Boolean
(standardens standardcompare
metod användningar(<=)
i dess genomförande)
definierar
-
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
Direkt superklass
monoid
Typer som instabiliserar Monoid
inkluderar listor, nummer och funktioner med Monoid
returvärden, bland andra. För att installa Monoid
måste en typ stödja en associativ binär operation ( mappend
eller (<>)
) som kombinerar dess värden och har ett speciellt "noll" mempty
( mempty
) så att en kombination av ett värde inte ändrar det värdet:
mempty <> x == x
x <> mempty == x
x <> (y <> z) == (x <> y) <> z
Intuitivt är Monoid
typer "listliknande" genom att de stödjer bifogade värden tillsammans. Alternativt kan Monoid
betraktas som sekvenser av värden för vilka vi bryr oss om ordningen men inte gruppering. Till exempel är ett binärt träd en Monoid
, men med hjälp av Monoid
operationerna kan vi inte bevittna dess grenstruktur, bara en genomgång av dess värden (se Foldable
och Traversable
).
Obligatoriska metoder
-
mempty :: Monoid m => m
-
mappend :: Monoid m => m -> m -> m
Direkt superklass
Ingen
Num
Den mest allmänna klassen för nummertyper, mer exakt för ringar , dvs siffror som kan läggas till och subtraheras och multipliceras i vanlig mening, men inte nödvändigtvis delas.
Denna klass innehåller både integrerade typer ( Int
, Integer
, Word32
etc.) och bråkstyper ( Double
, Rational
, också komplexa siffror etc.). Vid finita typer förstås semantiken generellt som modulär aritmetik , dvs med över- och underflöde † .
Observera att reglerna för de numeriska klasserna följs mycket mindre strikt än monad- eller monoidlagarna, eller reglerna för jämställdhetsjämförelse . I synnerhet följer antalet flytande punkter generellt bara lagar i ungefärlig mening.
Metoderna
fromInteger :: Num a => Integer -> a
. konvertera ett heltal till den allmänna siffertypen (om nödvändigt). Haskell- antalet bokstäver kan förstås som ett monomorfaltInteger
bokstavligt med den allmänna konverteringen runt, så att du kan använda bokstäverna5
i både ettInt
sammanhang och enComplex Double
dubbelinställning.(+) :: Num a => a -> a -> a
. Standardtillägg, allmänt förstått som associerande och kommutativ, dvs.a + (b + c) ≡ (a + b) + c a + b ≡ b + a
(-) :: Num a => a -> a -> a
. Subtraktion, som är det inversa av tillägg:(a - b) + b ≡ (a + b) - b ≡ a
(*) :: Num a => a -> a -> a
. Multiplikation, en associerande operation som är fördelaktig över tillägg:a * (b * c) ≡ (a * b) * c a * (b + c) ≡ a * b + a * c
för de vanligaste fallen är multiplikation också kommutativ, men detta är definitivt inte ett krav.
negate :: Num a => a -> a
. Det fullständiga namnet på operatören med unary negation.-1
är syntaktiskt socker förnegate 1
.-a ≡ negate a ≡ 0 - a
abs :: Num a => a -> a
. Funktionen absolutvärde ger alltid ett icke-negativt resultat av samma storlekabs (-a) ≡ abs a abs (abs a) ≡ abs a
abs a ≡ 0
bör bara hända oma ≡ 0
.För verkliga typer är det tydligt vad icke-negativt betyder: du har alltid
abs a >= 0
. Komplexa etc.-typer har inte en väldefinierad beställning, men resultatet avabs
bör alltid ligga i den verkliga underuppsättningen ‡ (dvs ge ett nummer som också kan skrivas som ett enda bokstavligt bokstav utan negation).signum :: Num a => a -> a
. Teckenfunktionen ger enligt namnet endast-1
eller1
, beroende på argumentets tecken. Egentligen är det bara sant för icke-andra verkliga siffror; i allmänhet förståssignum
bättre som normaliseringsfunktionen :abs (signum a) ≡ 1 -- unless a≡0 signum a * abs a ≡ a -- This is required to be true for all Num instances
Observera att avsnitt 6.4.4 i Haskell 2010-rapporten uttryckligen kräver att denna sista jämlikhet ska gälla för alla giltiga
Num
instanser.
Vissa bibliotek, särskilt linjär och hmatrix , har en mycket slappare förståelse för vad Num
klassen är för: de behandlar det bara som ett sätt att överbelasta de aritmetiska operatörerna . Även om detta är ganska enkelt för +
och -
, blir det redan besvärligt med *
och mer så med de andra metoderna. Bör *
innebära matrismultiplikation eller elementmässig multiplikation?
Det är utan tvekan en dålig idé att definiera sådana fall som inte är nummer. tänk på dedikerade klasser som VectorSpace
.
† I synnerhet lindas "negativerna" av osignerade typer till stora positiva, t.ex. (-4 :: Word32) == 4294967292
.
‡ Detta uppfylls ofta inte : vektortyper har inte en riktig delmängd. De kontroversiella Num
inställningarna för sådana typer definierar i allmänhet abs
och signum
elementmässigt, vilket matematiskt sett inte riktigt är vettigt.