2015-09-07 6 views
5

Sto scrivendo una piccola libreria per l'interazione con alcune API esterne. Un insieme di funzioni costruisce una richiesta valida per l'API di yahoo e analizza il risultato in un tipo di dati. Un altro set di funzioni cercherà la posizione attuale dell'utente in base all'IP e restituirà un tipo di dati che rappresenta la posizione corrente. Mentre il codice funziona, sembra che debba associare in modo esplicito la corrispondenza alla sequenza di più funzioni di tipo IO (forse a).Funzioni di concatenamento di tipo IO (Forse a)

-- Yahoo API 

constructQuery :: T.Text -> T.Text -> T.Text 
constructQuery city state = "select astronomy, item.condition from weather.forecast" <> 
          " where woeid in (select woeid from geo.places(1)" <> 
          " where text=\"" <> city <> "," <> state <> "\")" 

buildRequest :: T.Text -> IO ByteString 
buildRequest yql = do 
    let root = "https://query.yahooapis.com/v1/public/yql" 
     datatable = "store://datatables.org/alltableswithkeys" 
     opts = defaults & param "q" .~ [yql] 
          & param "env" .~ [datatable] 
          & param "format" .~ ["json"] 
    r <- getWith opts root 
    return $ r ^. responseBody 

run :: T.Text -> IO (Maybe Weather) 
run yql = buildRequest yql >>= (\r -> return $ decode r :: IO (Maybe Weather)) 


-- IP Lookup 
getLocation:: IO (Maybe IpResponse) 
getLocation = do 
    r <- get "http://ipinfo.io/json" 
    let body = r ^. responseBody 
    return (decode body :: Maybe IpResponse) 

- Combinator

runMyLocation:: IO (Maybe Weather) 
runMyLocation = do 
    r <- getLocation 
    case r of 
     Just ip -> getWeather ip 
     _ -> return Nothing 
    where getWeather = (run . (uncurry constructQuery) . (city &&& region)) 

E 'possibile infilare getLocation e correre insieme senza ricorrere a modello esplicita corrispondenza di "uscire" del Forse Monade?

risposta

3

Alcuni considerano questo un anti-pattern, ma è possibile utilizzare MaybeT IO a anziché IO (Maybe a). Il problema è che hai a che fare solo con uno dei modi in cui getLocation può fallire con —, ma potrebbe anche generare un'eccezione IO. Da quella prospettiva, potresti anche lasciar cadere lo Maybe e lanciare la tua eccezione se la decodifica fallisce, catturandola ovunque tu voglia.

+2

A proposito, perché questo è un anti-pattern? – Yuuri

+4

@Yuuri, perché sembra che otterrai sempre un risultato o "Niente", ma potresti ottenere, ad esempio, un'eccezione di timeout di rete. – dfeuer

+0

Non vedo perché pensi che sembri "sempre un risultato di' Nothing' ". La maggior parte degli Haskeller dovrebbe essere abbastanza familiare con i trasformatori monad per sapere come funzionano "dentro e fuori". – leftaroundabout

5

si può tranquillamente nido do blocchi che corrispondono a diverse monadi, quindi è bene avere un blocco di tipo Maybe Weather nel bel mezzo della vostra IO (Maybe Weather) blocco.

Per esempio,

runMyLocation :: IO (Maybe Weather) 
runMyLocation = do 
    r <- getLocation 
    return $ do ip <- r; return (getWeather ip) 
    where 
    getWeather = run . (uncurry constructQuery) . (city &&& region) 

Questo semplice modello do a <- r; return f a indica che non è necessario l'istanza Monade per Maybe a tutti anche se - una semplice fmap basta

runMyLocation :: IO (Maybe Weather) 
runMyLocation = do 
    r <- getLocation 
    return (fmap getWeather r) 
    where 
    getWeather = run . (uncurry constructQuery) . (city &&& region) 

e ora si vede che lo stesso motivo appare di nuovo, quindi puoi scrivere

runMyLocation :: IO (Maybe Weather) 
runMyLocation = fmap (fmap getWeather) getLocation 
    where 
    getWeather = run . (uncurry constructQuery) . (city &&& region) 

in cui l'esterno fmap esegue il mapping sull'azione IO e l'interno fmap esegue il mapping sul valore Maybe.


ho frainteso il tipo di getWeather (vedi commento qui sotto) in modo tale che si finirà con IO (Maybe (IO (Maybe Weather))) piuttosto che IO (Maybe Weather).

Quello che ti serve è un "join" attraverso uno stack monad a due livelli. Questo è essenzialmente ciò che un trasformatore monade prevede per voi (vedi @ risposta di dfeuer), ma è possibile scrivere questo combinatore manualmente nel caso di Maybe -

import Data.Maybe (maybe) 

flatten :: (Monad m) => m (Maybe (m (Maybe a))) -> m (Maybe a) 
flatten m = m >>= fromMaybe (return Nothing) 

nel qual caso è possibile scrivere

runMyLocation :: IO (Maybe Weather) 
runMyLocation = flatten $ fmap (fmap getWeather) getLocation 
    where 
    getWeather = run . (uncurry constructQuery) . (city &&& region) 

che dovrebbe avere il tipo corretto. Se intendi collegare più funzioni come questa, avrai bisogno di più chiamate allo flatten, nel qual caso potrebbe essere più semplice costruire invece una pila di trasformatori monad (con l'avvertenza nella risposta di @ dfeuer).

Probabilmente c'è un nome canonico per la funzione che ho chiamato "flatten" nelle librerie di trasformatori o mtl, ma al momento non riesco a trovarlo.

Si noti che la funzione fromMaybe da Data.Maybe essenzialmente analizza il caso per voi, ma lo riassume in una funzione.

+0

Apprezzo la risposta, tuttavia ciò comporta una funzione con tipo: runMyLocation :: IO (Maybe (IO (Maybe Weather))). Un modo per evitare il nidificazione di IO (Forse (IO Forse v))? – user2726995

+0

Mi scuso, ho interpretato erroneamente il tipo di 'getWeather' come' IpResponse -> Weather' piuttosto che 'IpResponse :: IO (Maybe Weather)'. Fornirò una modifica chiarificatrice. –

0

cambiare getWeather per avere Maybe IpResponse->IO.. e utilizzare >>= per implementarlo e quindi è possibile eseguire getLocation >>= getWeather. Lo >>= in getWeather è quello di Maybe, che si occuperà di Just and Nothing e l'altro getLocation>>= getWeather quello di IO.
puoi anche estrarre da Maybe e utilizzare qualsiasi Monad: getWeather :: Monad m -> m IpResponse -> IO .. e funzionerà.

+0

Non sono sicuro di cosa stai cercando di dire qui, ma qualsiasi cosa sembra essere mancante. – dfeuer

+0

cambia getWeather per avere Forse IpResponse-> IO .. e usa >> = per implementarlo e quindi puoi fare getLocation >> = getWeather. Il parametro >> = in getWeather è quello di Maybe, che si occuperà di Just and Nothing e dell'altra getLocation >> = getWeather di IO –

+0

Puoi modificare la tua risposta per includere questo suggerimento. Non mi piace personalmente, ma è legittimo. Così com'è, la tua risposta non è una risposta. – dfeuer

Problemi correlati