2015-12-08 23 views
7

Desidero comporre sequenzialmente due azioni monad in Haskell, scartando qualsiasi valore prodotto dal secondo e passando l'argomento ad entrambe le azioni. Attualmente sto usando un fai-block come questo:Aggiungere un'azione senza modificare il risultato alla do-notazione refactor

ask = do 
    result <- getLine 
    putStrLn result 
    return result 

Speravo di scrivere questo punto un po 'più libero e pulito, così ho provato questo:

ask' = getLine <* putStrLn 

Tuttavia, questo doesn' t anche digitare check e il problema è che <* non trasferisce il risultato della prima azione al secondo. Voglio concatenare le azioni come fa >>=, ma non modificare il risultato. Il tipo deve essere (a -> m b) -> (a -> m c) -> (a -> m b), ma Hoogle non produce risultati adeguati. Quale sarebbe un operatore per ottenere questa composizione di funzioni?

+1

Così si vuole 'GetLine >> = \ x -> x putStrLn >> ritorno x' in stile libero punto. Hai chiesto lambdabot? Dice 'liftM2 (>>) putStrLn return = << getLine'. –

+0

@ ThomasM.DuBuisson Sembra che Lambdabot abbia trovato quasi la funzione esatta che l'OP voleva! 'flip (liftM2 (>>)) :: Monad m => (a -> mb) -> (a -> mc) -> (a -> mb)' - il tipo attuale è leggermente più generalizzato quindi è un po ' difficile da vedere. – user2407038

+0

@ ThomasM.DuBuisson Grazie, ho chiesto lo strumento da riga di comando 'pointfree', e non poteva gestire' do', quindi ho rinunciato. Ho finito per usare 'ask = getLine >> = liftM2 (>>) putStrLn return', che sembra ok, grazie ancora! Puoi metterlo in una risposta se vuoi, quindi posso contrassegnarlo come risolto. – amoebe

risposta

8

come una tendenza, se si utilizza un valore in due luoghi diversi probabilmente è una buona idea dargli un nome in modo chiaro do blocco, piuttosto che premendo su stile inutile.

Il concetto astratto di suddivisione del flusso di informazioni per diverse azioni viene acquisito da cartesian monoidal categories, noto a Haskellers come arrows. Nel tuo caso, si sta praticamente lavorando nella categoria IO Kleisli:

import Prelude hiding (id) 
import Control.Arrow 

ask' :: Kleisli IO() String 
ask' = Kleisli (\()->getLine) >>> (putStrLn &&& id) >>> arr snd 

non penso che sia una buona idea di scrivere tale codice.

+0

Hai ragione, probabilmente non è una buona idea. Le frecce sembrano davvero complicate. Non sono sicuro se la soluzione con 'liftM2' sia abbastanza chiara o confusa? – amoebe

+0

Il problema con 'liftM2 (>>)' è che mischia due diverse monadi ('(a ->)' e 'IO') in un modo davvero molto contorto. Se fai qualcosa del genere, meglio farlo correttamente con 'ReaderT', ma vale la pena solo se stai leggendo lo stesso valore da un sacco di posti. – leftaroundabout

2

Desidero comporre sequenzialmente due azioni monad in Haskell, scartando qualsiasi valore prodotto dal secondo e passando l'argomento ad entrambe le azioni.

questo suona per me come un Reader -la funzione di tipo r -> m a è isomorfo a ReaderT r m a, e la monade opere implicitamente collegando lo stesso r valore in tutti i "buchi". Così, per esempio:

import Control.Applicative 
import Control.Monad.Reader 

example :: IO String 
example = getLine >>= discarding putStrLn 

discarding :: Monad m => (a -> m b) -> a -> m a 
discarding action = runReaderT (ReaderT action *> ask) 

L'operatore invece si è poi qualcosa di simile:

action `thingy` extra = action >>= discarding extra 

Ma naturalmente discarding dispone di un'implementazione più semplice:

discarding :: Applicative f => (a -> f b) -> a -> f a 
discarding action a = action a *> return a 

... quindi alla fine Penso che questo sia davvero il codice del golf. Ma in un programma più complesso in cui questo è un modello comune su scala più ampia, potrebbe valere la pena provare.In sostanza, se si dispone di:

a0 :: r -> m a0 
a1 :: r -> m a1 
    . 
    . 
    . 
an :: r -> m an 

allora ne consegue che:

ReaderT a0 :: ReaderT r m a0 
ReaderT a1 :: ReaderT r m a1 
    . 
    . 
    . 
ReaderT an :: ReaderT r m an 

E poi:

2

Per completezza, in questo caso particolare (la IO) Monade, si potrebbe abusare anche di bracket per questo scopo:

bracket getLine putStrLn return 

Ma lo sconsiglio fortemente, in quanto sarà molto meno leggibile rispetto al blocco originale do, è solo brutto.

Come già accennato, in questo caso specifico la denominazione del risultato sembra il modo migliore.

Vedi anche Should do-notation be avoided in Haskell?