Mi spiace, non conosco molto i miei calcoli, quindi sono curioso di sapere come pronunciare le funzioni nella classe di tipi applicativi
Conoscere la tua matematica, o no, è in gran parte irrilevante qui, penso. Come probabilmente saprai, Haskell prende in prestito alcuni bit di terminologia da vari campi della matematica astratta, in particolare la teoria delle categorie , da cui otteniamo funtori e monadi. L'uso di questi termini in Haskell differisce in qualche modo dalle definizioni matematiche formali, ma di solito sono abbastanza vicini da essere comunque buoni termini descrittivi.
La Applicative
classe type si trova da qualche parte tra Functor
e Monad
, quindi ci si aspetterebbe che abbia una base matematica simile. La documentazione per il Control.Applicative
modulo inizia con:
Questo modulo descrive una struttura intermedia tra un funtore e una monade: fornisce espressioni e sequenze pure, ma nessun legame. (Tecnicamente, un forte funtore monoidale lassista.)
Hmm.
class (Functor f) => StrongLaxMonoidalFunctor f where
. . .
Non così orecchiabile come Monad
, credo.
Ciò a cui tutto ciò si riduce fondamentalmente è che Applicative
non corrisponde a nessun concetto che sia particolarmente interessante dal punto di vista matematico, quindi non ci sono termini già pronti in giro che catturino il modo in cui è usato in Haskell. Quindi, per ora metti da parte la matematica.
Se vogliamo sapere come chiamarlo (<*>)
, potrebbe essere utile sapere cosa significa fondamentalmente.
Quindi cosa succede con Applicative
, in ogni caso, e perché non lo chiamiamo così?
Ciò Applicative
che in pratica equivale a un modo per trasformare funzioni arbitrarie in un file Functor
. Considera la combinazione di Maybe
(probabilmente il più semplice non banale Functor
) e Bool
(allo stesso modo il più semplice tipo di dati non banale).
maybeNot :: Maybe Bool -> Maybe Bool
maybeNot = fmap not
La funzione fmap
ci consente di passare not
dal lavoro Bool
al lavoro Maybe Bool
. Ma cosa succede se vogliamo sollevare (&&)
?
maybeAnd' :: Maybe Bool -> Maybe (Bool -> Bool)
maybeAnd' = fmap (&&)
Beh, questo non è quello che vogliamo a tutti ! In effetti, è praticamente inutile. Possiamo cercare di essere intelligente e infiliamo un altro Bool
nella Maybe
attraverso la parte posteriore ...
maybeAnd'' :: Maybe Bool -> Bool -> Maybe Bool
maybeAnd'' x y = fmap ($ y) (fmap (&&) x)
... ma non va bene. Per prima cosa, è sbagliato. Per un'altra cosa, è brutto . Potremmo continuare a provare, ma risulta che non c'è modo di sollevare una funzione di più argomenti per lavorare su un arbitrarioFunctor
. Fastidioso!
D'altra parte, potremmo farlo facilmente se Maybe
usassimo l' Monad
istanza di:
maybeAnd :: Maybe Bool -> Maybe Bool -> Maybe Bool
maybeAnd x y = do x' <- x
y' <- y
return (x' && y')
Ora, questo è un sacco di problemi solo per tradurre una semplice funzione - che è il motivo per cui Control.Monad
fornisce una funzione per farlo automaticamente, liftM2
. Il 2 nel suo nome si riferisce al fatto che funziona su funzioni di esattamente due argomenti; esistono funzioni simili per funzioni a 3, 4 e 5 argomenti. Queste funzioni sono migliori , ma non perfette, e specificare il numero di argomenti è brutto e goffo.
Il che ci porta al documento che ha introdotto la classe di tipo Applicativo . In esso, gli autori fanno essenzialmente due osservazioni:
- Trasformare funzioni multi-argomento in a
Functor
è una cosa molto naturale da fare
- Ciò non richiede le funzionalità complete di un file
Monad
L'applicazione della funzione normale è scritta da una semplice giustapposizione di termini, quindi per rendere "applicazione sollevata" il più semplice e naturale possibile, il documento introduce operatori infissi che sostituiscono l'applicazione, sollevati nelFunctor
, e una classe di tipo per fornire ciò che è necessario per quello .
Tutto ciò ci porta al punto seguente: (<*>)
rappresenta semplicemente l'applicazione della funzione, quindi perché pronunciarla in modo diverso da come si fa con lo spazio bianco "operatore di giustapposizione"?
Ma se questo non è molto soddisfacente, possiamo osservare che il Control.Monad
modulo fornisce anche una funzione che fa la stessa cosa per le monadi:
ap :: (Monad m) => m (a -> b) -> m a -> m b
Dov'è ap
, ovviamente, l'abbreviazione di "applica". Dato che any Monad
può essere Applicative
, e ap
necessita solo del sottoinsieme delle funzionalità presenti in quest'ultimo, possiamo forse dire che se (<*>)
non fosse un operatore, dovrebbe essere chiamato ap
.
Possiamo anche avvicinarci alle cose dall'altra direzione. L' Functor
operazione di sollevamento è chiamata fmap
perché è una generalizzazione map
dell'operazione su liste. Che tipo di funzione negli elenchi funzionerebbe (<*>)
? C'è quello che ap
fa sugli elenchi, ovviamente, ma non è particolarmente utile da solo.
In effetti, c'è un'interpretazione forse più naturale per le liste. Cosa ti viene in mente quando guardi la seguente firma del tipo?
listApply :: [a -> b] -> [a] -> [b]
C'è qualcosa di così allettante nell'idea di allineare le liste in parallelo, applicando ciascuna funzione nella prima all'elemento corrispondente della seconda. Sfortunatamente per il nostro vecchio amico Monad
, questa semplice operazione viola le leggi della monade se le liste sono di diversa lunghezza. Ma fa una multa Applicative
, nel qual caso (<*>)
diventa un modo per mettere insieme una versione generalizzata di zipWith
, quindi forse possiamo immaginare di chiamarla fzipWith
?
Questa idea lampo ci porta effettivamente al punto di partenza. Ricordi quella roba di matematica prima, sui funtori monoidali? Come suggerisce il nome, questi sono un modo per combinare la struttura di monoidi e funtori, entrambi familiari classi di tipo Haskell:
class Functor f where
fmap :: (a -> b) -> f a -> f b
class Monoid a where
mempty :: a
mappend :: a -> a -> a
Come sarebbero questi se li mettessi insieme in una scatola e lo scuotessi un po '? Da Functor
manterremo l'idea di una struttura indipendente dal suo parametro di tipo , e da Monoid
manterremo la forma complessiva delle funzioni:
class (Functor f) => MonoidalFunctor f where
mfEmpty :: f ?
mfAppend :: f ? -> f ? -> f ?
Non vogliamo presumere che ci sia un modo per creare un vero "vuoto" Functor
e non possiamo evocare un valore di un tipo arbitrario, quindi aggiusteremo il tipo di mfEmpty
as f ()
.
Inoltre, non vogliamo forzare la mfAppend
necessità di un parametro di tipo coerente, quindi ora abbiamo questo:
class (Functor f) => MonoidalFunctor f where
mfEmpty :: f ()
mfAppend :: f a -> f b -> f ?
A cosa serve il tipo di risultato mfAppend
? Abbiamo due tipi arbitrari di cui non sappiamo nulla, quindi non abbiamo molte opzioni. La cosa più sensata è mantenere entrambi:
class (Functor f) => MonoidalFunctor f where
mfEmpty :: f ()
mfAppend :: f a -> f b -> f (a, b)
A quel punto mfAppend
ora è chiaramente una versione generalizzata di zip
elenchi e possiamo ricostruire Applicative
facilmente:
mfPure x = fmap (\() -> x) mfEmpty
mfApply f x = fmap (\(f, x) -> f x) (mfAppend f x)
Questo ci mostra anche che pure
è correlato all'elemento identità di a Monoid
, quindi altri nomi validi potrebbero essere qualsiasi cosa che suggerisca un valore unitario, un'operazione nulla o simili.
È stato lungo, quindi per riassumere:
(<*>)
è solo un'applicazione per funzioni modificate, quindi puoi leggerlo come "ap" o "apply", oppure eliminarlo del tutto come faresti con una normale applicazione per funzioni.
(<*>)
generalizza anche approssimativamente zipWith
sugli elenchi, quindi puoi leggerlo come "zip funtori con", in modo simile alla lettura fmap
come "mappa un funtore con".
Il primo è più vicino all'intento della Applicative
classe di tipo, come suggerisce il nome, quindi è quello che consiglio.
In effetti, incoraggio l'uso liberale e la non pronuncia di tutti gli operatori di applicazioni sollevate :
(<$>)
, che trasforma una funzione con un solo argomento in un file Functor
(<*>)
, che concatena una funzione con più argomenti tramite un file Applicative
(=<<)
, che lega una funzione che immette a Monad
su un calcolo esistente
Tutti e tre sono, in fondo, solo normali applicazioni funzionali, un po 'speziate.