Haskell Language
Classi di tipo
Ricerca…
introduzione
I typeclasses in Haskell sono un mezzo per definire il comportamento associato a un tipo separatamente dalla definizione di quel tipo. Considerando che, in Java, si definirà il comportamento come parte della definizione del tipo, ad esempio in un'interfaccia, una classe astratta o una classe concreta, Haskell mantiene separate queste due cose.
Ci sono un certo numero di typeclass già definiti nel pacchetto base
di Haskell. La relazione tra questi è illustrata nella sezione Note sotto.
Osservazioni
Il seguente diagramma tratto dall'articolo Typeclassopedia mostra la relazione tra i vari typeclass in Haskell.
Forse e la classe dei Functor
In Haskell, i tipi di dati possono avere argomenti proprio come le funzioni. Prendi il tipo Maybe
per esempio.
Maybe
è un tipo molto utile che ci permette di rappresentare l'idea di fallimento, o la sua possibilità. In altre parole, se c'è una possibilità che un calcolo fallisca, usiamo il tipo Maybe
lì. Maybe
funziona come un wrapper per altri tipi, dando loro funzionalità aggiuntive.
La sua dichiarazione effettiva è abbastanza semplice.
Maybe a = Just a | Nothing
Ciò che dice è che un Maybe
presenta in due forme, un Just
, che rappresenta il successo, e un Nothing
, che rappresenta un fallimento. Prende Just
un argomento che determina il tipo di Maybe
e Nothing
non ne accetta. Ad esempio, il valore Just "foo"
avrà tipo Maybe String
, che è un tipo di stringa avvolto con la funzionalità Maybe
aggiuntiva. Il valore Nothing
ha digitare Maybe a
cui a
può essere di qualsiasi tipo.
Questa idea di tipi di wrapping per dare loro funzionalità aggiuntive è molto utile ed è applicabile a più di solo Maybe
. Altri esempi includono i tipi Either
, IO
e list, ciascuno con funzionalità diverse. Tuttavia, ci sono alcune azioni e abilità che sono comuni a tutti questi tipi di wrapper. Il più notevole di questi è la possibilità di modificare il valore incapsulato.
È normale pensare a questi tipi di tipi come scatole in cui possono essere inseriti valori. Scatole diverse contengono valori diversi e fanno cose diverse, ma nessuna è utile senza poter accedere ai contenuti all'interno.
Per incapsulare questa idea, Haskell viene fornito con una classe tipo standard, chiamata Functor
. È definito come segue.
class Functor f where
fmap :: (a -> b) -> f a -> f b
Come si può vedere, la classe ha una singola funzione, fmap
, di due argomenti. Il primo argomento è una funzione da un tipo, a
, a un altro, b
. Il secondo argomento è un functor (tipo wrapper) che contiene un valore di tipo a
. Restituisce un functor (tipo wrapper) contenente un valore di tipo b
.
In termini semplici, fmap
assume una funzione e si applica al valore all'interno di un functor. È l'unica funzione necessaria affinché un tipo sia membro della classe Functor
, ma è estremamente utile. Le funzioni che operano su funtori che hanno applicazioni più specifiche possono essere trovate nelle tipecche Applicative
e Monad
.
Tipo di ereditarietà della classe: classe di tipo Ord
Haskell supporta una nozione di estensione di classe. Ad esempio, la classe Ord
eredita tutte le operazioni Eq
, ma in aggiunta ha una funzione di compare
che restituisce un Ordering
tra valori. Ord
può anche contenere gli operatori di confronto degli ordini comuni, nonché un metodo min
e un metodo max
.
La notazione =>
ha lo stesso significato che ha in una firma di funzione e richiede il tipo a
per implementare l' Eq
, al fine di implementare 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
Tutti i metodi che seguono il compare
possono essere derivati da esso in diversi modi:
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
Digitare le classi che estendono esse stesse Ord
deve implementare almeno il metodo di compare
o il metodo (<=)
stesso, che crea il reticolo di ereditarietà diretto.
Eq
Tutti i tipi di dati di base (come Int
, String
, Eq a => [a]
) di Prelude tranne le funzioni e IO
hanno istanze di Eq
. Se un tipo istanzia l' Eq
significa che sappiamo come confrontare due valori per valore o uguaglianza strutturale .
> 3 == 2
False
> 3 == 3
True
Metodi richiesti
-
(==) :: Eq a => a -> a -> Boolean
o(/=) :: Eq a => a -> a -> Boolean
(se solo uno è implementato, l'altro valore predefinito è la negazione del definito uno)
definisce
-
(==) :: Eq a => a -> a -> Boolean
-
(/=) :: Eq a => a -> a -> Boolean
Superclassi dirette
Nessuna
Sottoclassi degne di nota
ord
I tipi che istanziano Ord
includono, ad esempio, Int
, String
e [a]
(per i tipi a
dove c'è un'istanza Ord a
un'istanza). Se un tipo crea un'istanza di Ord
significa che conosciamo un ordine "naturale" di valori di quel tipo. Nota, ci sono spesso molte possibili scelte dell'ordine "naturale" di un tipo e Ord
ci costringe a favorirne uno.
Ord
fornisce gli operatori standard (<=)
, (<)
, (>)
, (>=)
ma li definisce tutti in modo interessante utilizzando un tipo di dati algebrico personalizzato
data Ordering = LT | EQ | GT
compare :: Ord a => a -> a -> Ordering
Metodi richiesti
-
compare :: Ord a => a -> a -> Ordering
o(<=) :: Ord a => a -> a -> Boolean
(il metodo dicompare
predefinito dello standard utilizza(<=)
nella sua implementazione)
definisce
-
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
Superclassi dirette
monoid
I tipi che istanziano Monoid
includono liste, numeri e funzioni con i valori di ritorno Monoid
, tra gli altri. Per istanziare Monoid
un tipo deve supportare un'operazione binaria associativa ( mappend
o (<>)
) che combina i suoi valori e avere un valore speciale "zero" ( mempty
) tale che combinando un valore con esso non cambia quel valore:
mempty <> x == x
x <> mempty == x
x <> (y <> z) == (x <> y) <> z
Intuitivamente, i tipi Monoid
sono "list-like" in quanto supportano insieme i valori. In alternativa, i tipi Monoid
possono essere pensati come sequenze di valori per i quali ci preoccupiamo dell'ordine ma non del raggruppamento. Per esempio, un albero binario è un Monoid
, ma usando le operazioni Monoid
non possiamo vedere la sua struttura ramificata, ma solo un attraversamento dei suoi valori (vedi Foldable
e Traversable
).
Metodi richiesti
-
mempty :: Monoid m => m
-
mappend :: Monoid m => m -> m -> m
Superclassi dirette
Nessuna
Num
La classe più generale per i tipi di numeri, più precisamente per gli anelli , cioè i numeri che possono essere aggiunti e sottratti e moltiplicati nel senso comune, ma non necessariamente divisi.
Questa classe contiene sia tipi interi ( Int
, Integer
, Word32
ecc.) Che tipi frazionari ( Double
, Rational
, anche numeri complessi ecc.). In caso di tipi finiti, la semantica è generalmente intesa come aritmetica modulare , cioè con sovra- e underflow † .
Si noti che le regole per le classi numeriche sono osservate molto meno rigorosamente delle leggi monade o monoide, o quelle per il confronto di uguaglianza . In particolare, i numeri a virgola mobile generalmente obbediscono alle leggi solo in senso approssimativo.
I metodi
fromInteger :: Num a => Integer -> a
. convertire un numero intero nel tipo di numero generale (avvolgendo l'intervallo, se necessario). I numeri letterali di Haskell possono essere interpretati come unInteger
monomorfico con la conversione generale attorno ad esso, quindi puoi usare il letterale5
sia in un contestoInt
sia in un'impostazioneComplex Double
.(+) :: Num a => a -> a -> a
. Aggiunta standard, generalmente intesa come associativa e commutativa, cioè,a + (b + c) ≡ (a + b) + c a + b ≡ b + a
(-) :: Num a => a -> a -> a
. Sottrazione, che è l'inverso dell'aggiunta:(a - b) + b ≡ (a + b) - b ≡ a
(*) :: Num a => a -> a -> a
. Moltiplicazione, un'operazione associativa che è distributiva oltre l'aggiunta:a * (b * c) ≡ (a * b) * c a * (b + c) ≡ a * b + a * c
per le istanze più comuni, anche la moltiplicazione è commutativa, ma questo non è assolutamente un requisito.
negate :: Num a => a -> a
. Il nome completo dell'operatore di negazione unario.-1
è zucchero sintattico pernegate 1
.-a ≡ negate a ≡ 0 - a
abs :: Num a => a -> a
. La funzione valore assoluto fornisce sempre un risultato non negativo della stessa grandezzaabs (-a) ≡ abs a abs (abs a) ≡ abs a
abs a ≡ 0
dovrebbe accadere solo sea ≡ 0
.Per i tipi reali è chiaro cosa significa non negativo: hai sempre
abs a >= 0
. I tipi complessi ecc. Non hanno un ordinamento ben definito, tuttavia il risultato degliabs
dovrebbe sempre trovarsi nel sottoinsieme reale ‡ (cioè dare un numero che potrebbe anche essere scritto come un singolo numero letterale senza negazione).signum :: Num a => a -> a
. La funzione del segno, in base al nome, produce solo-1
o1
, a seconda del segno dell'argomento. In realtà, questo è vero solo per numeri reali diversi da zero; in generale ilsignum
è meglio inteso come la funzione di normalizzazione :abs (signum a) ≡ 1 -- unless a≡0 signum a * abs a ≡ a -- This is required to be true for all Num instances
Si noti che la sezione 6.4.4 del Report Haskell 2010 richiede esplicitamente che quest'ultima equazione sia valida per qualsiasi istanza
Num
valida.
Alcune librerie, in particolare lineare e hmatrix , hanno una comprensione molto più lenta di ciò che la classe Num
è per: trattano proprio come un modo per sovraccaricare gli operatori aritmetici . Mentre questo è piuttosto semplice per +
e -
, diventa già problematico con *
e molto altro con gli altri metodi. Ad esempio, dovrebbe *
indicare la moltiplicazione della matrice o la moltiplicazione degli elementi?
È discutibilmente una cattiva idea definire tali istanze non numeriche; per favore considera classi dedicate come VectorSpace
.
† In particolare, i "negativi" dei tipi non firmati sono racchiusi in ampi positivi, ad esempio (-4 :: Word32) == 4294967292
.
‡ Questo è ampiamente non soddisfatto: i tipi di vettore non hanno un vero sottoinsieme. Il controverso Num
istanze per tali tipi generalmente definisce abs
e signum
-in-element, che matematicamente non ha senso.