2011-09-22 5 views
13

Ho scritto codice con il seguente schema più volte di recente, e mi chiedevo se c'era un modo più breve per scriverlo.Esiste già una funzione come questa? (Oppure, qual è il nome migliore per questa funzione?)

foo :: IO String 
foo = do 
    x <- getLine 
    putStrLn x >> return x 

per rendere le cose un po 'più pulito, ho scritto questa funzione (anche se non sono sicuro che sia un nome appropriato):

constM :: (Monad m) => (a -> m b) -> a -> m a 
constM f a = f a >> return a 

posso poi fare foo in questo modo:

foo = getLine >>= constM putStrLn 

Esiste già una funzione/un idioma come questo? E se no, qual è il nome migliore per la mia constM?

+4

Sembra che sia possibile definire questa funzione utilizzando semplici funzioni: 'constF :: Functor f => (a -> f b) -> a -> f a; constF f a = a <$ f a' – fuz

+0

@FUZxxl, anche questo funziona, grazie ... è questo uso corretto del nome const allora? –

+0

Cosa intendi? – fuz

risposta

18

Bene, consideriamo i modi in cui qualcosa come questo potrebbe essere essere semplificata. Una versione non monadica potrebbe sembrare qualcosa come const' f a = const a (f a), che è chiaramente equivalente a flip const con un tipo più specifico. Con la versione monadica, tuttavia, il risultato di f a può fare cose arbitrarie alla struttura non parametrica del funtore (ad esempio, quelli che vengono spesso chiamati "effetti collaterali"), comprese le cose che dipendono dal valore di a. Ciò che questo ci dice è che, nonostante fingiamo di avere un come se stessimo scartando il risultato di f a, in realtà non stiamo facendo nulla del genere. Restituisce a invariato poiché la parte parametrica del functor è molto meno essenziale e potremmo sostituire return con qualcos'altro e avere ancora una funzione concettualmente simile.

Quindi la prima cosa che possiamo concludere è che può essere visto come un caso particolare di una funzione come la seguente:

doBoth :: (Monad m) => (a -> m b) -> (a -> m c) -> a -> m c 
doBoth f g a = f a >> g a 

Da qui, ci sono due modi diversi di guardare per una struttura di fondo di qualche tipo.


Una prospettiva è di riconoscere lo schema di frazionamento un singolo argomento tra più funzioni, poi ricombinare i risultati. Questo è il concetto incarnato dai Applicative/Monad casi per funzioni, in questo modo:

doBoth :: (Monad m) => (a -> m b) -> (a -> m c) -> a -> m c 
doBoth f g = (>>) <$> f <*> g 

...o, se si preferisce:

doBoth :: (Monad m) => (a -> m b) -> (a -> m c) -> a -> m c 
doBoth = liftA2 (>>) 

Naturalmente, liftA2 è equivalente a liftM2 così si potrebbe chiedere se il sollevamento un'operazione su monadi in un altro monade ha qualcosa a che fare con i trasformatori monade; in generale, il rapporto non è imbarazzante, ma in questo caso funziona facilmente, dando qualcosa di simile:

doBoth :: (Monad m) => ReaderT a m b -> ReaderT a m c -> ReaderT a m c 
doBoth = (>>) 

... modulo di avvolgimento appropriate e quali, naturalmente. Per tornare alla versione originale, l'uso originale di return ora deve essere qualcosa con tipo ReaderT a m a, che non dovrebbe essere troppo difficile da riconoscere come la funzione ask per le monodie dei lettori.


L'altra prospettiva è riconoscere che funzioni con tipi come (Monad m) => a -> m bpossono essere composti direttamente, proprio come funzioni pure. La funzione (<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c) fornisce un equivalente diretto alla composizione di funzione (.) :: (b -> c) -> (a -> b) -> (a -> c) oppure è possibile invece utilizzare Control.Category e il wrapper newtypeKleisli per lavorare con la stessa cosa in modo generico.

Abbiamo ancora bisogno di dividere l'argomento, tuttavia, così che cosa abbiamo veramente bisogno qui è una composizione "ramificazione", che Category da sola non ha; utilizzando Control.Arrow così otteniamo (&&&), facendoci riscrivere la funzione come segue:

doBoth :: (Monad m) => Kleisli m a b -> Kleisli m a c -> Kleisli m a (b, c) 
doBoth f g = f &&& g 

Dato che non ci interessa circa il risultato della prima freccia Kleisli, solo i suoi effetti collaterali, possiamo scartare che la metà di la tupla in modo ovvio:

doBoth :: (Monad m) => Kleisli m a b -> Kleisli m a c -> Kleisli m a c 
doBoth f g = f &&& g >>> arr snd 

Che ci riporta al modulo generico. Specializzata per l'originale, il return ora diventa semplicemente id:

constKleisli :: (Monad m) => Kleisli m a b -> Kleisli m a a 
constKleisli f = f &&& id >>> arr snd 

Poiché le funzioni regolari sono anche Arrow s, la definizione di cui sopra funziona anche lì se si generalizza la firma tipo. Tuttavia, può essere illuminante per espandere la definizione che ne deriva per funzioni pure e semplificare come segue:

  • \f x -> (f &&& id >>> arr snd) x
  • \f x -> (snd . (\y -> (f y, id y))) x
  • \f x -> (\y -> snd (f y, y)) x
  • \f x -> (\y -> y) x
  • \f x -> x.

Quindi siamo tornati allo flip const, come previsto!


In breve, la funzione è una certa variazione su entrambi (>>) o flip const, ma in modo che si basa sulle differenze - ex utilizzando sia un ambiente ReaderT e (>>) della monade sottostante, quest'ultimo utilizza gli effetti collaterali impliciti dello specifico Arrow e l'aspettativa che gli effetti collaterali Arrow si verifichino in un ordine particolare. A causa di questi dettagli, non è probabile che vi siano generalizzazioni o semplificazioni disponibili. In un certo senso, la definizione che stai usando è esattamente tanto semplice quanto deve essere, motivo per cui le definizioni alternative che ho dato sono più lunghe e/o implicano una certa quantità di wrapping e unwrapping.

Una funzione come questa sarebbe un'aggiunta naturale di una "libreria di utilità monad" di qualche tipo. Mentre Control.Monad fornisce alcuni combinatori lungo queste linee, è tutt'altro che esauriente e non potrei né trovare né richiamare alcuna variazione su questa funzione nelle librerie standard. Non sarei affatto sorpreso di trovarlo in una o più librerie di utilità su hackage, tuttavia.

Avendo in gran parte rinunciato alla questione dell'esistenza, non posso davvero offrire molte indicazioni su come nominare al di là di ciò che è possibile trarre dalla discussione precedente sui concetti correlati.

Come ultimo accenno, si noti anche che la propria funzione non ha scelte di flusso di controllo basate sul risultato di un'espressione monadica, poiché l'esecuzione delle espressioni non importa quale sia l'obiettivo principale. Avere una struttura computazionale indipendente dal contenuto parametrico (vale a dire la roba di tipo Monad m => m a) è in genere un segno che non è effettivamente necessario un intero Monad, e potrebbe ottenere con la nozione più generale di Applicative.

+0

Non una risposta alla sua domanda. +1 comunque per una grande analisi. :-) – luqui

+0

@luqui: Beh, si pensava che fosse una sorta di spiegazione rotonda di ricerca della struttura sottostante per trovare generalizzazioni di qualcosa, dato che la funzione * esatta * descritta non esiste ... ma sì, questo tipo di si è perso da qualche parte lungo la strada. Probabilmente dovresti rivedere questo a un certo punto per renderlo più chiaro. –

1

Io in realtà non ho un indizio questo esiste esattamente giá, ma si vede questo molto in parser-generatori solo con nomi diversi (ad esempio, per ottenere i cosa parentesi all'interno) - lì è normalmente una sorta di operatore (>>. e .>> in fparsec per esempio) per questo. Per dare davvero un nome, forse lo chiamerei ignora?

+1

Ho visto persone usare il nome 'ignore' per' ignore :: Monad m => m a -> m() ',' ignora m = m >> return() '. –

+0

@Judah Sopprimere l'avvertimento sull'eliminazione del risultato di una dichiarazione di approvazione? – fuz

+0

Non sono sicuro che l'ignora sia corretta poiché richiedo la restituzione del valore dato. –

3

Hmm, non credo che constM sia appropriato qui.

map :: (a -> b) -> [a] -> [b] 
mapM :: (Monad m) => (a -> m b) -> [a] -> m b 

const :: b -> a -> b 

Quindi, forse:

constM :: (Monad m) => b -> m a -> m b 
constM b m = m >> return b 

La funzione che si sono M -ing sembra essere:

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

che non ha scelta se non quella di ignorare il suo primo argomento. Quindi questa funzione non ha molto da dire puramente.

vedo come un modo per, hmm, osservare un valore con un effetto lato. observe, effect, sideEffect possono essere nomi decenti. observe è il mio preferito, ma forse solo perché è accattivante, non perché sia ​​chiaro.

+0

Penso che osservare sia più chiaro del mio nome, e immagino che non abbia bisogno di una "M" poiché un puro equivalente a questo non avrebbe alcun senso. –

+0

Sono dubbioso su "osservare". Certamente non osserviamo il valore prodotto dalla funzione, e poiché la funzione richiede solo 'Applicativo' non osserveremo necessariamente alcun effetto collaterale. –