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.

Relazioni tra classi di tipo Haskell standard, Figura 1 come pubblicato in Typeclassopedia.

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 di compare 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 un Integer monomorfico con la conversione generale attorno ad esso, quindi puoi usare il letterale 5 sia in un contesto Int sia in un'impostazione Complex 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 per negate 1 .

      -a ≡ negate a ≡ 0 - a
    
  • abs :: Num a => a -> a . La funzione valore assoluto fornisce sempre un risultato non negativo della stessa grandezza

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

    abs a ≡ 0 dovrebbe accadere solo se a ≡ 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 degli abs 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 o 1 , a seconda del segno dell'argomento. In realtà, questo è vero solo per numeri reali diversi da zero; in generale il signum è 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.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow