Una monade è solo un monoide nella categoria degli endofunctor, qual è il problema?


723

Chi ha detto per primo quanto segue?

Una monade è solo un monoide nella categoria degli endofunctor, qual è il problema?

E su una nota meno importante, è vero e in tal caso potresti dare una spiegazione (si spera che possa essere compresa da qualcuno che non ha molta esperienza con Haskell)?


14
Vedi "Categorie per il matematico che lavora"
Don Stewart,

19
Non è necessario capirlo per usare le monadi in Haskell. Da un punto di vista pratico sono solo un modo intelligente per passare in "stato" attraverso un impianto idraulico sotterraneo.
Starblue,

1
Vorrei aggiungere questo eccellente post sul blog anche qui: stephendiehl.com/posts/monads.html Non risponde direttamente alla domanda, ma secondo me Stephen fa un ottimo lavoro nel legare insieme categorie e monadi a Haskell. Se hai letto le risposte sopra - questo dovrebbe aiutare a unificare i due modi di vedere questo.
Ben Ford,

3
Più precisamente "Per qualsiasi categoria C, la categoria [C, C] dei suoi endofunctor ha una struttura monoidale indotta dalla composizione. Un oggetto monoide in [C, C] è una monade su C." - da en.wikipedia.org/wiki/Monoid_%28category_theory%29. Vedi en.wikipedia.org/wiki/Monad_%28category_theory%29 per la definizione di monade nella teoria delle categorie.

1
@Dmitry Un functor è una funzione tra le categorie, con alcuni vincoli da comportarsi bene. Un endofunctor su una categoria C è solo un funzione da C a se stesso. Data.Functor è una tabella di tipi per endofunctor nella categoria Hask . Poiché una categoria è costituita da oggetti e morfismi, un funzione deve mappare entrambi. Per un'istanza f di Data.Functor, la mappa sugli oggetti (tipi di haskell) è f stessa e la mappa sui morfismi (funzioni di haskell) è fmap.
Matthijs,

Risposte:


796

Quel particolare fraseggio è di James Iry, dalla sua storia dei linguaggi di programmazione breve, incompleta e per lo più errata, molto divertente , in cui la attribuisce immaginariamente a Philip Wadler.

La citazione originale è di Saunders Mac Lane in Categories for the Working Mathematician , uno dei testi fondamentali della teoria delle categorie. Qui è nel contesto , che è probabilmente il posto migliore per imparare esattamente cosa significa.

Ma prenderò un colpo. La frase originale è questa:

Tutto sommato, una monade in X è solo un monoide nella categoria degli endofunctor di X, con il prodotto × sostituito dalla composizione degli endofunctor e dall'unità impostata dall'identificatore endofunctor.

X qui è una categoria. Gli endofunctor sono funzionali da una categoria a sé (che di solito è tutto Functor per quanto riguarda i programmatori funzionali, dato che si occupano principalmente di una sola categoria; la categoria dei tipi - ma sto divagando). Ma potresti immaginare un'altra categoria che è la categoria di "endofunctor su X ". Questa è una categoria in cui gli oggetti sono endofunctor e i morfismi sono trasformazioni naturali.

E di quegli endofunctor, alcuni potrebbero essere monadi. Quali sono le monadi? Esattamente quelli che sono monoidali in un senso particolare. Invece di precisare l'esatta mappatura dalle monadi ai monoidi (dal momento che Mac Lane fa molto meglio di quanto potessi sperare), metterò le loro rispettive definizioni fianco a fianco e ti consentirò di confrontare:

Un monoide è ...

  • Un set, S
  • Un'operazione, •: S × S → S
  • Un elemento di S , e: 1 → S

... soddisfare queste leggi:

  • (a • b) • c = a • (b • c) , per tutte a , b e c in S
  • e • a = a • e = a , per tutti a in S

Una monade è ...

  • Un endofunctor, T: X → X (in Haskell, un costruttore di tipo di tipo * -> *con Functorun'istanza)
  • Una trasformazione naturale, μ: T × T → T , dove × significa composizione del funzione ( μ è noto come joinin Haskell)
  • Una trasformazione naturale, η: I → T , dove I è l'identità endofunctor su X ( η è noto come returnin Haskell)

... soddisfare queste leggi:

  • μ ∘ Tμ = μ ∘ μT
  • μ ∘ Tη = μ ∘ ηT = 1 (la trasformazione naturale dell'identità)

Con un po 'di strabismo potresti riuscire a vedere che entrambe queste definizioni sono istanze dello stesso concetto astratto .


21
grazie per la spiegazione e grazie per l'articolo Breve, incompleto e per lo più sbagliato della storia dei linguaggi di programmazione. Ho pensato che potesse essere da lì. Davvero uno dei più grandi pezzi di umorismo di programmazione.
Roman A. Taycher,

6
@Jonathan: nella formulazione classica di un monoide, × indica il prodotto cartesiano degli insiemi. Potete leggere di più su questo qui: en.wikipedia.org/wiki/Cartesian_product , ma l'idea di base è che un elemento di S × T è una coppia (s, t) , dove s ∈ S e t ∈ T . Quindi la firma del prodotto monoideale •: S × S -> S in questo contesto significa semplicemente una funzione che accetta 2 elementi di S come input e produce un altro elemento di S come output.
Tom Crockett,

12
@TahirHassan - Nella generalità della teoria delle categorie, ci occupiamo di "oggetti" opachi invece di insiemi, e quindi non esiste una nozione a priori di "elementi". Ma se pensate alla categoria Set in cui gli oggetti sono impostati e le frecce sono funzioni, gli elementi di qualsiasi set S sono in corrispondenza uno a uno con le funzioni di qualsiasi set di un elemento su S. Cioè, per qualsiasi elemento e di S , c'è esattamente una funzione f: 1 -> S , dove 1 è un qualsiasi set di un elemento ... (continua)
Tom Crockett

12
@TahirHassan I set di 1 elemento sono essi stessi specializzazioni della più generale nozione teorica di categoria di "oggetti terminali": un oggetto terminale è qualsiasi oggetto di una categoria per il quale vi è esattamente una freccia da qualsiasi altro oggetto su di esso (è possibile verificare che questo vale per i set di 1 elemento in Set ). Nella teoria delle categorie gli oggetti terminali sono semplicemente indicati come 1 ; sono unici fino all'isomorfismo, quindi non ha senso distinguerli. Quindi ora abbiamo una descrizione puramente teorica di categoria di "elementi di S " per ogni S : sono solo le frecce da 1 a S !
Tom Crockett,

7
@TahirHassan - Per dirlo in termini di Haskell, pensa al fatto che se Sè un tipo, tutto ciò che puoi fare quando scrivi una funzione f :: () -> Sè scegliere un particolare termine di tipo S(un "elemento" di esso, se vuoi) e tornare esso ... non ti è stata data alcuna informazione reale con l'argomento, quindi non c'è modo di variare il comportamento della funzione. Quindi fdeve essere una funzione costante che restituisce sempre la stessa cosa ogni volta. ()("Unità") è l'oggetto terminale della categoria Hask e non è un caso che esista esattamente 1 valore (non divergente) che lo abita.
Tom Crockett,

532

Intuitivamente, penso che ciò che il fantastico vocabolario matematico stia dicendo sia che:

monoid

Un monoide è un insieme di oggetti e un metodo per combinarli. I monoidi ben noti sono:

  • numeri che puoi aggiungere
  • elenchi che puoi concatenare
  • imposta che puoi unire

Ci sono anche esempi più complessi.

Inoltre, ogni monoide ha un'identità , che è quell'elemento "no-op" che non ha alcun effetto quando lo si combina con qualcos'altro:

  • 0 + 7 == 7 + 0 == 7
  • [] ++ [1,2,3] == [1,2,3] ++ [] == [1,2,3]
  • {} union {apple} == {apple} union {} == {apple}

Infine, un monoide deve essere associativo . (puoi ridurre una lunga serie di combinazioni come vuoi, purché non modifichi l'ordine da sinistra a destra degli oggetti) L'aggiunta è OK ((5 + 3) +1 == 5+ (3+ 1)), ma la sottrazione non è ((5-3) -1! = 5- (3-1)).

Monade

Consideriamo ora un tipo speciale di set e un modo speciale di combinare oggetti.

Oggetti

Supponiamo che il tuo set contenga oggetti di un tipo speciale: funzioni . E queste funzioni hanno una firma interessante: non portano numeri su numeri o stringhe su stringhe. Invece, ogni funzione trasporta un numero in un elenco di numeri in un processo in due passaggi.

  1. Calcola 0 o più risultati
  2. Combina quei risultati in una sola risposta in qualche modo.

Esempi:

  • 1 -> [1] (basta avvolgere l'input)
  • 1 -> [] (scartare l'input, avvolgere il nulla in un elenco)
  • 1 -> [2] (aggiungi 1 all'input e avvolgi il risultato)
  • 3 -> [4, 6] (aggiungi 1 all'input, e moltiplica l'input per 2 e racchiudi i risultati multipli )

Combinazione di oggetti

Inoltre, il nostro modo di combinare le funzioni è speciale. Un modo semplice per combinare la funzione è la composizione : prendiamo i nostri esempi sopra e componiamo ciascuna funzione con se stessa:

  • 1 -> [1] -> [[1]] (avvolge l'input, due volte)
  • 1 -> [] -> [] (scartare l'input, avvolgere il nulla in un elenco, due volte)
  • 1 -> [2] -> [UH-OH! ] (non possiamo "aggiungere 1" a un elenco! ")
  • 3 -> [4, 6] -> [UH-OH! ] (non possiamo aggiungere 1 un elenco!)

Senza entrare troppo nella teoria dei tipi, il punto è che puoi combinare due numeri interi per ottenere un numero intero, ma non puoi sempre comporre due funzioni e ottenere una funzione dello stesso tipo. (Le funzioni con il tipo a -> a comporranno, ma a-> [a] no.)

Quindi, definiamo un modo diverso di combinare le funzioni. Quando uniamo due di queste funzioni, non vogliamo "racchiudere in doppio" i risultati.

Ecco cosa facciamo. Quando vogliamo combinare due funzioni F e G, seguiamo questo processo (chiamato binding ):

  1. Calcola i "risultati" da F ma non combinarli.
  2. Calcola i risultati applicando G a ciascuno dei risultati di F separatamente, producendo una raccolta di raccolta di risultati.
  3. Appiattisci la raccolta su 2 livelli e combina tutti i risultati.

Tornando ai nostri esempi, combiniamo (associamo) una funzione con se stessa usando questo nuovo modo di "legare" le funzioni:

  • 1 -> [1] -> [1] (avvolgere l'ingresso, due volte)
  • 1 -> [] -> [] (scartare l'input, avvolgere il nulla in un elenco, due volte)
  • 1 -> [2] -> [3] (aggiungi 1, quindi aggiungi di nuovo 1 e avvolgi il risultato.)
  • 3 -> [4,6] -> [5,8,7,12] (aggiungi 1 all'input e moltiplica anche l'input per 2, mantenendo entrambi i risultati, quindi esegui nuovamente tutto per entrambi i risultati, quindi avvolgi il finale risulta in un elenco.)

Questo modo più sofisticato di combinare le funzioni è associativo (a seguito di come la composizione delle funzioni è associativa quando non si eseguono le fantasiose funzioni di avvolgimento).

Legando tutto insieme,

  • una monade è una struttura che definisce un modo per combinare (i risultati di) funzioni,
  • analogamente a come un monoide è una struttura che definisce un modo per combinare oggetti,
  • dove il metodo di combinazione è associativo,
  • e dove esiste uno speciale "No-op" che può essere combinato con qualsiasi cosa per ottenere qualcosa di invariato.

Appunti

Esistono molti modi per "racchiudere" i risultati. È possibile creare un elenco o un set o scartare tutti tranne il primo risultato osservando se non ci sono risultati, allegare un sidecar di stato, stampare un messaggio di registro, ecc. Ecc.

Ho giocato un po 'con le definizioni nella speranza di ottenere l'idea essenziale attraverso intuitivamente.

Ho semplificato un po 'le cose insistendo sul fatto che la nostra monade opera su funzioni di tipo a -> [a] . In effetti, le monadi lavorano su funzioni di tipo a -> mb , ma la generalizzazione è una specie di dettaglio tecnico che non è la comprensione principale.


22
Questa è una bella spiegazione di come ogni monade costituisce una categoria (la categoria Kleisli è ciò che stai dimostrando - c'è anche la categoria Eilenberg-Moore). Ma a causa del fatto che non si può comporre alcun due frecce Kleisli a -> [b]e c -> [d](si può fare solo se b= c), questo non abbastanza descrive un monoide. In realtà è l'operazione di appiattimento che hai descritto, piuttosto che la composizione della funzione, che è "l'operatore monoide".
Tom Crockett,

6
Certo, se limitassi una monade a un solo tipo, ovvero se permetti solo le frecce Kleisli del modulo a -> [a], questo sarebbe un monoide (perché ridurrai la categoria Kleisli a un singolo oggetto e qualsiasi categoria di un solo oggetto è per definizione un monoide!), ma non catturerebbe la piena generalità della monade.
Tom Crockett,

5
Nell'ultima nota, è utile ricordare che a -> [a] è solo un -> [] a. ([] è anche solo costruttore di tipi.) E quindi non può essere visto solo come -> mb, ma [] è in effetti un'istanza della classe Monad.
Evi1M4chine

8
Questa è la spiegazione migliore e più incisiva delle monadi e del loro background matematico di monoidi che ho trovato letteralmente in settimane. Questo è ciò che dovrebbe essere stampato in ogni libro di Haskell quando si tratta di monadi, a mani basse. Upvote! Forse otterremo ulteriormente l'informazione, che le monadi sono realizzate come istanze di typeclass parametrizzate che avvolgono tutto ciò che viene inserito in haskell, nel post. (Almeno è così che li ho capiti ormai. Correggimi se sbaglio. Vedi haskell.org/haskellwiki/What_a_Monad_is_not )
sjas

1
Questo è fantastico - è l'unica spiegazione che ho capito abbastanza bene da poterlo spiegare a qualcun altro ... Ma ancora non capisco perché questo sia un modo prezioso di pensare a qualcosa. :(
Adam Barnes,

84

Innanzitutto, le estensioni e le librerie che utilizzeremo:

{-# LANGUAGE RankNTypes, TypeOperators #-}

import Control.Monad (join)

Di questi, RankNTypesè l'unico che è assolutamente essenziale per il seguito. Una volta ho scritto una spiegazione RankNTypesche alcune persone sembrano aver trovato utile , quindi mi riferirò a quello.

Citando l'eccellente risposta di Tom Crockett , abbiamo:

Una monade è ...

  • Un endofunctor, T: X -> X
  • Una trasformazione naturale, μ: T × T -> T , dove × significa composizione di funzioni
  • Una trasformazione naturale, η: I -> T , dove I è l'identificatore endofunctor su X

... soddisfare queste leggi:

  • μ (μ (T × T) × T)) = μ (T × μ (T × T))
  • μ (η (T)) = T = μ (T (η))

Come lo traduciamo in codice Haskell? Bene, iniziamo con l'idea di una trasformazione naturale :

-- | A natural transformations between two 'Functor' instances.  Law:
--
-- > fmap f . eta g == eta g . fmap f
--
-- Neat fact: the type system actually guarantees this law.
--
newtype f :-> g =
    Natural { eta :: forall x. f x -> g x }

Un tipo di forma f :-> gè analogo a un tipo di funzione, ma invece di pensarlo come una funzione tra due tipi (di tipo *), pensalo come un morfismo tra due funzioni (ognuna di tipo * -> *). Esempi:

listToMaybe :: [] :-> Maybe
listToMaybe = Natural go
    where go [] = Nothing
          go (x:_) = Just x

maybeToList :: Maybe :-> []
maybeToList = Natural go
    where go Nothing = []
          go (Just x) = [x]

reverse' :: [] :-> []
reverse' = Natural reverse

Fondamentalmente, in Haskell, le trasformazioni naturali sono funzioni da un tipo f xa un altro tipo in modo g xtale che la xvariabile di tipo sia "inaccessibile" al chiamante. Quindi, per esempio, sort :: Ord a => [a] -> [a]non può essere trasformato in una trasformazione naturale, perché è "esigente" su quali tipi possiamo istanziare a. Un modo intuitivo che uso spesso per pensare a questo è il seguente:

  • Un funzione è un modo di operare sul contenuto di qualcosa senza toccare la struttura .
  • Una trasformazione naturale è un modo di operare sulla struttura di qualcosa senza toccare o guardare il contenuto .

Ora, a parte questo, affrontiamo le clausole della definizione.

La prima clausola è "un endofunctor, T: X -> X ". Bene, ogni FunctorHaskell è un endofunctor in ciò che la gente chiama "la categoria Hask", i cui oggetti sono tipi di Haskell (di tipo *) e i cui morfismi sono funzioni di Haskell. Sembra un'affermazione complicata, ma in realtà è molto banale. Tutto ciò che significa è che a Functor f :: * -> *ti dà i mezzi per costruire un tipo f a :: *per qualsiasi a :: *e una funzione fmap f :: f a -> f bda qualsiasi f :: a -> b, e che questi obbediscono alle leggi dei funzioni .

Seconda clausola: il Identityfunctor in Haskell (che viene fornito con la piattaforma, quindi puoi semplicemente importarlo) è definito in questo modo:

newtype Identity a = Identity { runIdentity :: a }

instance Functor Identity where
    fmap f (Identity a) = Identity (f a)

Quindi la trasformazione naturale η: I -> T dalla definizione di Tom Crockett può essere scritta in questo modo per qualsiasi Monadistanza t:

return' :: Monad t => Identity :-> t
return' = Natural (return . runIdentity)

Terza clausola: la composizione di due funzioni in Haskell può essere definita in questo modo (che viene fornita anche con la piattaforma):

newtype Compose f g a = Compose { getCompose :: f (g a) }

-- | The composition of two 'Functor's is also a 'Functor'.
instance (Functor f, Functor g) => Functor (Compose f g) where
    fmap f (Compose fga) = Compose (fmap (fmap f) fga)

Quindi la trasformazione naturale μ: T × T -> T dalla definizione di Tom Crockett può essere scritta in questo modo:

join' :: Monad t => Compose t t :-> t
join' = Natural (join . getCompose)

L'affermazione che questo è un monoide nella categoria di endofunctor significa quindi che Compose(parzialmente applicato solo ai suoi primi due parametri) è associativo, e questo Identityè il suo elemento di identità. Vale a dire che valgono i seguenti isomorfismi:

  • Compose f (Compose g h) ~= Compose (Compose f g) h
  • Compose f Identity ~= f
  • Compose Identity g ~= g

Questi sono molto facili da dimostrare perché Composee Identitysono entrambi definiti come newtype, e i Rapporti Haskell definiscono la semantica di newtypecome un isomorfismo tra il tipo che viene definito e il tipo di argomento per il newtypecostruttore di dati. Ad esempio, proviamo Compose f Identity ~= f:

Compose f Identity a
    ~= f (Identity a)                 -- newtype Compose f g a = Compose (f (g a))
    ~= f a                            -- newtype Identity a = Identity a
Q.E.D.

Nel Naturalnewtype, non riesco a capire cosa (Functor f, Functor g)sta facendo il vincolo. Potresti spiegare?
dfeuer

@dfeuer In realtà non sta facendo nulla di essenziale.
Luis Casillas,

1
@LuisCasillas Ho rimosso quei Functorvincoli poiché non sembrano necessari. Se non sei d'accordo, sentiti libero di aggiungerli di nuovo.
Lambda Fairy,

Puoi approfondire cosa significa formalmente che il prodotto di funzioni sia preso come composizione? In particolare, quali sono i morfismi di proiezione per la composizione di funzioni? La mia ipotesi è che il prodotto è definito solo per un funzione F contro se stesso, F x F e solo quando joinè definito. E questo joinè il morfismo della proiezione. Ma non sono sicuro.
tksfz,

6

Nota: No, questo non è vero. Ad un certo punto c'è stato un commento su questa risposta dello stesso Dan Piponi dicendo che la causa e l'effetto qui erano esattamente l'opposto, che ha scritto il suo articolo in risposta alla battuta di James Iry. Ma sembra essere stato rimosso, forse da un po 'di ordine compulsivo.

Di seguito è la mia risposta originale.


È possibile che Iry abbia letto From Monoids to Monads , un post in cui Dan Piponi (sigfpe) deriva monadi dai monoidi in Haskell, con molte discussioni sulla teoria delle categorie e una menzione esplicita della "categoria degli endofunctor su Hask ". In ogni caso, chiunque si chieda cosa significhi per una monade essere un monoide nella categoria degli endofunctor potrebbe trarre beneficio dalla lettura di questa derivazione.


1
"Forse con un po 'di ordine compulsivo" - o, come ci riferiamo con affetto a loro su questo sito, un moderatore :-).
halfer

6

Sono arrivato a questo post per capire meglio l'inferenza della famigerata citazione della teoria della categoria per il matematico di lavoro di Mac Lane .

Nel descrivere ciò che è qualcosa, è spesso altrettanto utile descrivere ciò che non lo è.

Il fatto che Mac Lane usi la descrizione per descrivere una Monade, si potrebbe implicare che descriva qualcosa di unico per le monadi. Sopportami. Per sviluppare una comprensione più ampia della dichiarazione, credo che deve essere chiarito che egli non descrivendo qualcosa che è unico per monadi; la dichiarazione descrive ugualmente Applicativo e Frecce tra gli altri. Per lo stesso motivo possiamo avere due monoidi su Int (somma e prodotto), possiamo avere diversi monoidi su X nella categoria di endofunctor. Ma c'è ancora di più nelle somiglianze.

Sia Monad che Applicative soddisfano i criteri:

  • endo => qualsiasi freccia, o morfismo che inizia e finisce nello stesso posto
  • functor => qualsiasi freccia o morfismo tra due categorie

    (ad es. giorno per giorno Tree a -> List b, ma nella categoria Tree -> List)

  • monoid => oggetto singolo; vale a dire un singolo tipo, ma in questo contesto, solo per quanto riguarda lo strato esterno; quindi, non possiamo avere Tree -> List, solo List -> List.

L'istruzione utilizza "Categoria di ..." Definisce l'ambito dell'istruzione. Ad esempio, la categoria Functor descrive l'ambito di f * -> g *, ovvero Any functor -> Any functor, ad esempio, Tree * -> List *o Tree * -> Tree *.

Ciò che un'istruzione categoriale non specifica descrive dove è permesso qualsiasi cosa .

In questo caso, all'interno dei funzioni, * -> *aka a -> bnon è specificato il che significa Anything -> Anything including Anything else. Mentre la mia immaginazione passa a Int -> String, include anche Integer -> Maybe Into anche Maybe Double -> Either String Intdove a :: Maybe Double; b :: Either String Int.

Quindi la dichiarazione si presenta come segue:

  • ambito di funzione :: f a -> g b(ovvero qualsiasi tipo di parametro con qualsiasi tipo di parametro)
  • endo + functor :: f a -> f b(cioè qualsiasi tipo di parametro con lo stesso tipo di parametro) ... detto in modo diverso,
  • un monoide nella categoria di endofunctor

Allora, dov'è il potere di questo costrutto? Per apprezzare la dinamica completa, avevo bisogno di vedere che i disegni tipici di un monoide (singolo oggetto con quella che sembra una freccia di identità :: single object -> single object), non riescono a illustrare che mi è permesso usare una freccia parametrizzata con un numero qualsiasi di valori monoid, dall'oggetto di un tipo consentito in Monoid. La definizione di equivalenza endo, ~ Identity Arrow ignora il valore del tipo del funzione e sia il tipo che il valore del livello "payload" più interno. Pertanto, l'equivalenza ritorna truein qualsiasi situazione in cui i tipi funzionali corrispondono (ad esempio, Nothing -> Just * -> Nothingequivale a Just * -> Just * -> Just *perché sono entrambi Maybe -> Maybe -> Maybe).

Sidebar: ~ outside è concettuale, ma è il simbolo più a sinistra in f a. Descrive anche ciò che "Haskell" legge per primo (quadro generale); quindi Type è "esterno" in relazione a un valore di tipo. La relazione tra livelli (una catena di riferimenti) nella programmazione non è facile da mettere in relazione in Categoria. La categoria di set viene utilizzata per descrivere i tipi (Int, String, Maybe Int ecc.) Che include la categoria di Functor (tipi con parametri). La catena di riferimento: Tipo di Functor, Valori di Functor (elementi dell'insieme di quel Functor, ad es. Nothing, Just) e, a sua volta, tutto il resto di ciascun valore di funzione. Nella categoria la relazione è descritta in modo diverso, ad esempio, return :: a -> m aè considerata una trasformazione naturale da un Functor a un altro Functor, diversa da qualsiasi cosa menzionata finora.

Tornando al thread principale, tutto sommato, per qualsiasi prodotto tensore definito e un valore neutro, l'affermazione finisce per descrivere un costrutto computazionale incredibilmente potente nato dalla sua struttura paradossale:

  • all'esterno appare come un singolo oggetto (es., :: List); statico
  • ma dentro, permette molte dinamiche
    • qualsiasi numero di valori dello stesso tipo (ad esempio, Vuoto | ~ Non vuoto) come foraggio per funzioni di qualsiasi arità. Il prodotto tensore ridurrà qualsiasi numero di input a un singolo valore ... per il livello esterno (~ foldche non dice nulla sul payload)
    • infinita gamma di sia il tipo e valori per lo strato più interno

In Haskell è importante chiarire l'applicabilità dell'affermazione. La potenza e la versatilità di questo costrutto non ha assolutamente nulla a che fare con una monade in . In altre parole, il costrutto non si basa su ciò che rende unica una monade.

Quando si cerca di capire se costruire codice con un contesto condiviso per supportare i calcoli che dipendono l'uno dall'altro, rispetto ai calcoli che possono essere eseguiti in parallelo, questa famigerata affermazione, con tutto ciò che descrive, non è in contrasto tra la scelta di Applicativo, Frecce e Monadi, ma piuttosto è una descrizione di quanto sono uguali. Per la decisione in corso, l'affermazione è controversa.

Questo è spesso frainteso. La dichiarazione prosegue descrivendo join :: m (m a) -> m acome il prodotto tensore per l'endofuncore monoidale. Tuttavia, non si spiega come anche nel contesto di questa affermazione si (<*>)possa scegliere. È davvero un esempio di sei / mezza dozzina. La logica per combinare i valori è esattamente uguale; lo stesso input genera lo stesso output da ciascuno (a differenza dei monoidi Sum e Product per Int perché generano risultati diversi quando si combinano Ints).

Quindi, per ricapitolare: un monoide nella categoria degli endofunctor descrive:

   ~t :: m * -> m * -> m *
   and a neutral value for m *

(<*>)ed (>>=)entrambi forniscono l'accesso simultaneo ai due mvalori per calcolare il singolo valore di ritorno. La logica utilizzata per calcolare il valore restituito è esattamente la stessa. Se non fosse per le diverse forme delle funzioni che parametrizzano ( f :: a -> brispetto a k :: a -> m b) e la posizione del parametro con lo stesso tipo di ritorno del calcolo (cioè, a -> b -> brispetto b -> a -> ba ciascuno rispettivamente), sospetto che avremmo potuto parametrizzare la logica monoidale, il prodotto tensore, per il riutilizzo in entrambe le definizioni. Come esercizio per chiarire il punto, prova ad attuare ~te finisci con (<*>)e in (>>=)base a come decidi di definirlo forall a b.

Se il mio ultimo punto è almeno concettualmente vero, allora spiega la precisa e unica differenza computazionale tra Applicativo e Monade: le funzioni che parametrizzano. In altre parole, la differenza è esterna all'implementazione di queste classi di tipi.

In conclusione, secondo la mia esperienza, la famigerata citazione di Mac Lane ha fornito un ottimo meme "goto", un punto di riferimento per me a cui fare riferimento mentre navigo attraverso la Categoria per comprendere meglio gli idiomi usati in Haskell. Riesce a catturare l'ambito di una potente capacità di elaborazione resa meravigliosamente accessibile a Haskell.

Tuttavia, c'è ironia nel modo in cui ho frainteso per la prima volta l'applicabilità dell'affermazione al di fuori della monade e ciò che spero abbia trasmesso qui. Tutto ciò che descrive risulta essere ciò che è simile tra Applicativo e Monadi (e Frecce tra gli altri). Ciò che non dice è precisamente la piccola ma utile distinzione tra loro.

- E


5

Le risposte qui fanno un ottimo lavoro nel definire sia i monoidi che le monadi, tuttavia, non sembrano ancora rispondere alla domanda:

E su una nota meno importante, è vero e in tal caso potresti dare una spiegazione (si spera che possa essere compresa da qualcuno che non ha molta esperienza con Haskell)?

Il nocciolo della questione che qui manca, è la diversa nozione di "monoide", la cosiddetta categorizzazione più precisamente - quella del monoide in una categoria monoidale. Purtroppo il libro stesso di Mac Lane lo rende molto confuso :

Detto ciò, una monade Xè solo un monoide nella categoria degli endofunctor X, con il prodotto ×sostituito dalla composizione degli endofunctor e dall'unità impostata dall'identificatore endofunctor.

Confusione principale

Perché questo è confuso? Perché non definisce ciò che è "monoide nella categoria di endofunctor" di X. Invece, questa frase suggerisce di prendere un monoide all'interno dell'insieme di tutti gli endofunctor insieme alla composizione di funzione come operazione binaria e il funzione di identità come unità monoida. Che funziona perfettamente e trasforma in un monoide qualsiasi sottoinsieme di endofunctor che contiene il funzione di identità ed è chiuso sotto la composizione del funzione.

Tuttavia questa non è l'interpretazione corretta, che il libro non riesce a chiarire in quella fase. Una Monade fè un endofunctor fisso , non un sottoinsieme di endofunctor chiusi sotto composizione. Una costruzione comune è usare fper generare un monoide prendendo l'insieme di tutte kle composizioni f^k = f(f(...))di fse stesso, incluso k=0quello corrispondente all'identità f^0 = id. E ora l'insieme Sdi tutti questi poteri per tutti k>=0è davvero un monoide "con prodotto × sostituito dalla composizione di endofunctor e unità impostati dall'identificatore endofunctor".

E ancora:

  • Questo monoide Spuò essere definito per qualsiasi funzione fo anche letteralmente per qualsiasi auto-mappa di X. È il monoide generato da f.
  • La struttura monoidale Sdata dalla composizione del funzione e dal funzione identità non ha nulla a che fare con l' fessere o non essere una monade.

E per rendere le cose più confuse, la definizione di "monoide nella categoria monoidale" arriva più avanti nel libro, come si può vedere dal sommario . Eppure comprendere questa nozione è assolutamente fondamentale per comprendere la connessione con le monadi.

(Rigorose) categorie monoidali

Andando al capitolo VII sui monoidi (che viene dopo il capitolo VI sulle monadi), troviamo la definizione della cosiddetta categoria monoidale rigorosa come tripla (B, *, e), dove Bè una categoria, *: B x B-> Bun bifunctor (funzione rispetto a ciascun componente con un altro componente fisso ) ed eè un oggetto unitario B, soddisfacendo l'associatività e le leggi unitarie:

(a * b) * c = a * (b * c)
a * e = e * a = a

per tutti gli oggetti a,b,cdi B, e le stesse identità per eventuali morfismi a,b,ccon esostituita da id_e, il morfismo identità e. È ora istruttivo osservare che nel nostro caso di interesse, dov'è Bla categoria di endofunctor Xcon trasformazioni naturali come morfismi, *composizione del funzione e eidentificatore, tutte queste leggi sono soddisfatte, come può essere verificato direttamente.

Ciò che segue nel libro è la definizione della categoria monoidale "rilassata" , in cui le leggi contengono solo alcune trasformazioni naturali fisse che soddisfano le cosiddette relazioni di coerenza , che tuttavia non è importante per i nostri casi delle categorie di endofunctor.

Monoidi in categorie monoidali

Infine, nella sezione 3 "Monoidi" del capitolo VII, viene data la definizione effettiva:

Un monoide cin una categoria monoidale (B, *, e)è un oggetto Bcon due frecce (morfismi)

mu: c * c -> c
nu: e -> c

rendendo commutativi 3 diagrammi. Ricordiamo che nel nostro caso si tratta di morfismi nella categoria degli endofunctor, che sono trasformazioni naturali corrispondenti esattamente joine returnper una monade. La connessione diventa ancora più chiara quando rendiamo la composizione *più esplicita, sostituendo c * ccon c^2, dov'è la cnostra monade.

Infine, si noti che i 3 diagrammi commutativi (nella definizione di un monoide nella categoria monoidale) sono scritti per categorie monoidali generali (non rigide), mentre nel nostro caso tutte le trasformazioni naturali derivanti dalla categoria monoidale sono in realtà identità. Ciò renderà i diagrammi esattamente uguali a quelli nella definizione di monade, completando la corrispondenza.

Conclusione

In sintesi, ogni monade è per definizione un endofunctor, quindi un oggetto nella categoria degli endofunctor, in cui il monadic joine gli returnoperatori soddisfano la definizione di monoid in quella particolare (rigorosa) categoria monoidale . Viceversa, qualsiasi monoide nella categoria monoideale di endofunctor è per definizione una tripla (c, mu, nu)costituita da un oggetto e due frecce, ad esempio trasformazioni naturali nel nostro caso, che soddisfano le stesse leggi di una monade.

Infine, nota la differenza chiave tra i monoidi (classici) e i monoidi più generali nelle categorie monoidali. Le due frecce mue nusopra non sono più un'operazione binaria e un'unità in un set. Invece, hai un endofunctor fisso c. La composizione del funzione *e il solo dispositivo di identità non forniscono la struttura completa necessaria per la monade, nonostante quell'osservazione confusa nel libro.

Un altro approccio sarebbe quello di confrontare con il monoide standard Cdi tutte le auto-mappe di un insieme A, in cui l'operazione binaria è la composizione, che può essere vista per mappare il prodotto cartesiano standard C x Cin C. Passando al monoide classificato, stiamo sostituendo il prodotto cartesiano xcon la composizione *del funzione e l'operazione binaria viene sostituita dalla trasformazione naturale muda c * ca c, ovvero una raccolta di joinoperatori

join: c(c(T))->c(T)

per ogni oggetto T(digitare in programmazione). E gli elementi di identità nei monoidi classici, che possono essere identificati con immagini di mappe da un set di un punto fisso, vengono sostituiti con la raccolta degli returnoperatori

return: T->c(T) 

Ma ora non ci sono più prodotti cartesiani, quindi nessuna coppia di elementi e quindi nessuna operazione binaria.


Qual è la tua risposta alla parte "è vero" della domanda? È vero che una monade è un monoide nella categoria degli endofunctor? E se sì, qual è la relazione tra la nozione di teoria delle categorie di un monoide e un monoide algebrico (un insieme con una moltiplicazione associativa e un'unità)?
Alexander Belopolsky,
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.