Haskell: come si pronuncia <*>? [chiuso]


109

Come si pronunciano queste funzioni nella classe di tipi applicativi:

(<*>) :: f (a -> b) -> f a -> f b
(*>)  :: f a -> f b -> f b
(<*)  :: f a -> f b -> f a

(Cioè, se non fossero operatori, come potrebbero essere chiamati?)

Come nota a margine, se potessi rinominare purein qualcosa di più amichevole per i non matematici, come lo chiameresti?


6
@ J Cooper ... potresti sentire come lo pronunciamo? :) Potresti voler pubblicare una richiesta su meta.stackoverflow.com per una funzione di registrazione e riproduzione vocale :).
Kiril

8
Si pronuncia "Santo cielo, stavano davvero esaurendo gli operatori, vero?" Inoltre, purepotrebbe essere un buon nome per makeApplicative.
Chuck

@Lirik, beh, immagino che per pronuncia intendo "whaddya chiama questa cosa" :) @ Chuck, posta il tuo puresuggerimento come risposta e ti darò un voto positivo
J Cooper

6
(<*>) è la versione Control.Applicative di "ap" di Control.Monad, quindi "ap" è probabilmente il nome più appropriato.
Edward KMETT

11
Lo chiamerei ciclope, ma sono solo io.
RCIX

Risposte:


245

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 Applicativeclasse type si trova da qualche parte tra Functore Monad, quindi ci si aspetterebbe che abbia una base matematica simile. La documentazione per il Control.Applicativemodulo 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 Applicativenon 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ò Applicativeche 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 fmapci consente di passare notdal lavoro Boolal 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 Boolnella Maybeattraverso 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 Maybeusassimo l' Monadistanza 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.Monadfornisce 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.Monadmodulo 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 Monadpuò essere Applicative, e apnecessita 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' Functoroperazione di sollevamento è chiamata fmapperché è una generalizzazione mapdell'operazione su liste. Che tipo di funzione negli elenchi funzionerebbe (<*>)? C'è quello che apfa 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 Functormanterremo l'idea di una struttura indipendente dal suo parametro di tipo , e da Monoidmanterremo 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" Functore non possiamo evocare un valore di un tipo arbitrario, quindi aggiusteremo il tipo di mfEmptyas f ().

Inoltre, non vogliamo forzare la mfAppendnecessità 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 mfAppendora è chiaramente una versione generalizzata di zipelenchi e possiamo ricostruire Applicativefacilmente:

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 zipWithsugli elenchi, quindi puoi leggerlo come "zip funtori con", in modo simile alla lettura fmapcome "mappa un funtore con".

Il primo è più vicino all'intento della Applicativeclasse 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 Monadsu un calcolo esistente

Tutti e tre sono, in fondo, solo normali applicazioni funzionali, un po 'speziate.


6
@Colin Cochrane: Sei sicuro di non aver sbagliato a scrivere "prolisso"? :) Ma hey, lo prendo! Ho sempre sentito che Applicativee lo stile idiomatico funzionale che promuove non ottengono abbastanza amore, quindi non ho potuto resistere alla possibilità di esaltare un po 'le sue virtù come mezzo per spiegare come (non) pronuncio (<*>).
CA McCann

+1! Sentiti libero di essere altrettanto prolisso su stackoverflow.com/questions/2104446/…
Greg Bacon

6
Se Haskell avesse lo zucchero di sintassi per Applicative's! Qualcosa di simile [| f a b c d |](come suggerito dal documento originale). Quindi non avremmo bisogno del <*>combinatore e faresti riferimento a un'espressione del genere come un esempio di "applicazione di funzioni in un contesto funtoriale"
Tom Crockett

1
@FredOverflow: No, volevo dire Monad. O Functoro Monoidqualsiasi altra cosa che abbia un termine ben definito che coinvolge meno di tre aggettivi. "Applicativo" è semplicemente un nome poco interessante, anche se ragionevolmente descrittivo, schiaffeggiato su qualcosa di cui ne aveva bisogno.
CA McCann

1
@pelotom: vedi [ stackoverflow.com/questions/12014524/... dove gente gentile mi ha mostrato due modi per ottenere quasi che la notazione.
AndrewC

21

Poiché non ho l'ambizione di migliorare la risposta tecnica di CA McCann , affronterò quella più soffice:

Se potessi rinominare purein qualcosa di più amichevole per i podunk come me, come lo chiameresti?

In alternativa, soprattutto perché non c'è fine alle continue grida piene di angoscia e tradimento contro la Monadversione, chiamata " return", propongo un altro nome, che suggerisce la sua funzione in un modo che può soddisfare il più imperativo dei programmatori imperativi , e la più funzionale di ... beh, si spera, ognuno può lamentare la stessa cosa: inject.

Prendi un valore. "Iniettare" in la Functor, Applicative, Monad, o quello che-hanno-te. Voto per " inject" e ho approvato questo messaggio.


4
Di solito tendo a qualcosa come "unità" o "portanza", ma questi hanno già troppi altri significati in Haskell. injectè un nome eccellente e probabilmente migliore del mio, anche se come nota secondaria minore, "inject" è usato - penso - in Smalltalk e Ruby per un metodo di piegatura a sinistra di qualche tipo. Non ho mai capito quella scelta del nome, però ...
CA McCann

3
Questo è un thread molto vecchio, ma penso che injectin Ruby & Smalltalk venga utilizzato perché è come se si "iniettasse" un operatore tra ogni elemento nell'elenco. Almeno, è così che l'ho sempre pensato.
Jonathan Sterling

1
Per riprendere di nuovo quel vecchio thread laterale: non stai iniettando operatori, stai sostituendo (eliminando) i costruttori che sono già presenti. (Visto il contrario, stai iniettando vecchi dati in un nuovo tipo.) Per gli elenchi, l'eliminazione è solo foldr. (Sostituisci (:)e [], dove (:)prende 2 argomenti ed []è una costante, quindi foldr (+) 0 (1:2:3:[])1+2+3+0.) Su Boolè solo if- then- else(due costanti, scegline uno) e poiché Maybesi chiama maybe... Haskell non ha un unico nome / funzione per questo, poiché tutti hanno tipi diversi (in generale l'eliminazione è solo ricorsione / induzione)
nessuno

@CAMcCann Smalltalk ha preso quel nome da una canzone di Arlo Guthrie sulla bozza per la guerra del Vietnam, in cui i giovani sfortunati venivano raccolti, selezionati, a volte rifiutati e, in altro modo, iniettati.
Tom Anderson

7

In breve:

  • <*>puoi chiamarlo applicare . Quindi Maybe f <*> Maybe apuò essere pronunciato come applicare Maybe fsopraMaybe a .

  • Puoi rinominare purein of, come fanno molte librerie JavaScript. In JS puoi creare un Maybecon Maybe.of(a).

Inoltre, il wiki di Haskell ha una pagina sulla pronuncia degli operatori linguistici qui


3
(<*>) -- Tie Fighter
(*>)  -- Right Tie
(<*)  -- Left Tie
pure  -- also called "return"

Fonte: Haskell Programming da First Principles , di Chris Allen e Julie Moronuki


Quei nomi non hanno esattamente preso piede, per quanto ne so.
dfeuer

@dfeuer aspetta la prossima generazione di Haskeller che utilizzano quel libro come materiale di apprendimento principale.
dmvianna

1
Potrebbe succedere. I nomi sono orribili, però, poiché non hanno alcun legame con i significati.
dfeuer

1
@dfeuer, non vedo una buona soluzione da nessuna parte. "ap" / "apply" è vago come "tie fighter". Tutto è applicazione di funzioni. Tuttavia, un nome che esce dal nulla potrebbe acquisire significato attraverso l'uso. "Apple" è un buon esempio. A proposito, il rendimento di Monad è puro applicativo. Niente invenzioni qui.
dmvianna
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.