2013-08-13 27 views
7

Sto cercando di analizzare un file utilizzando la funzione parseFile trovata nel pacchetto haskell-src-exts. Sto cercando di lavorare con l'output di parseFile che è, naturalmente, IO, ma non riesco a capire come aggirare l'IO. Ho trovato una funzione liftIO ma non sono sicuro se questa sia la soluzione in questa situazione. Ecco il codice qui sotto.Haskell: Trapped in IO monad

import Language.Haskell.Exts.Syntax 
import Language.Haskell.Exts 
import Data.Map hiding (foldr, map) 
import Control.Monad.Trans 

increment :: Ord a => a -> Map a Int -> Map a Int 
increment a = insertWith (+) a 1 

fromName :: Name -> String 
fromName (Ident s) = s 
fromName (Symbol st) = st 

fromQName :: QName -> String 
fromQName (Qual _ fn) = fromName fn 
fromQName (UnQual n) = fromName n 

fromLiteral :: Literal -> String 
fromLiteral (Int int) = show int 

fromQOp :: QOp -> String 
fromQOp (QVarOp qn) = fromQName qn 

vars :: Exp -> Map String Int 
vars (List (x:xs)) = vars x 
vars (Lambda _ _ e1) = vars e1 
vars (EnumFrom e1) = vars e1 
vars (App e1 e2) = unionWith (+) (vars e1) (vars e2) 
vars (Let _ e1) = vars e1 
vars (NegApp e1) = vars e1 
vars (Var qn) = increment (fromQName qn) empty 
vars (Lit l) = increment (fromLiteral l) empty 
vars (Paren e1) = vars e1 
vars (InfixApp exp1 qop exp2) = increment (fromQOp qop) $ unionWith (+) (vars exp1) (vars exp2) 



match :: [Match] -> Map String Int 
match rhss = foldr (unionWith (+)) empty (map (\(Match a b c d e f) -> rHs e) rhss) 

rHS :: GuardedRhs -> Map String Int 
rHS (GuardedRhs _ _ e1) = vars e1 

rHs':: [GuardedRhs] -> Map String Int 
rHs' gr = foldr (unionWith (+)) empty (map (\(GuardedRhs a b c) -> vars c) gr) 

rHs :: Rhs -> Map String Int 
rHs (GuardedRhss gr) = rHs' gr 
rHs (UnGuardedRhs e1) = vars e1 

decl :: [Decl] -> Map String Int 
decl decls = foldr (unionWith (+)) empty (map fun decls) 
    where fun (FunBind f) = match f 
      fun _ = empty 

pMod' :: (ParseResult Module) -> Map String Int 
pMod' (ParseOk (Module _ _ _ _ _ _ dEcl)) = decl dEcl 

pMod :: FilePath -> Map String Int 
pMod = pMod' . liftIO . parseFile 

Voglio solo essere in grado di utilizzare la funzione pMod 'sull'output di parseFile. Si noti che tutti i tipi e i costruttori di dati possono essere trovati a http://hackage.haskell.org/packages/archive/haskell-src-exts/1.13.5/doc/html/Language-Haskell-Exts-Syntax.html se questo aiuta. Grazie in anticipo!

risposta

16

Una volta dentro IO, non c'è via di fuga.

Uso fmap:

-- parseFile :: FilePath -> IO (ParseResult Module) 
-- pMod' :: (ParseResult Module) -> Map String Int 
-- fmap :: Functor f => (a -> b) -> f a -> f b 

-- fmap pMod' (parseFile filePath) :: IO (Map String Int) 

pMod :: FilePath -> IO (Map String Int) 
pMod = fmap pMod' . parseFile 

(Inoltre :) Come spiegato in great answer by Levi Pearson, c'è anche

Prelude Control.Monad> :t liftM 
liftM :: (Monad m) => (a1 -> r) -> m a1 -> m r 

Ma non è magia nera sia. Si consideri:

Prelude Control.Monad> let g f = (>>= return . f) 
Prelude Control.Monad> :t g 
g :: (Monad m) => (a -> b) -> m a -> m b 

Così il vostro funzione può anche essere scritta come

pMod fpath = fmap pMod' . parseFile $ fpath 
    = liftM pMod' . parseFile $ fpath 
    = (>>= return . pMod') . parseFile $ fpath -- pushing it... 
    = parseFile fpath >>= return . pMod'   -- that's better 

pMod :: FilePath -> IO (Map String Int) 
pMod fpath = do 
    resMod <- parseFile fpath 
    return $ pMod' resMod 

qualunque si trovare più intuitiva (ricordate, (.) ha la precedenza più alta, appena sotto l'applicazione funzioni).

Per inciso, il bit >>= return . f è come implementato in realtà liftM, solo in do -notazione; e si vede veramente l'equivalenza di fmap e liftM, perché per ogni monade si deve ritenere che:

fmap f m = m >>= (return . f) 
+0

Grazie mille, questo sembra funzionare molto bene! – user2548080

14

Per dare una risposta più generale di di Will (che era certamente corretto e to-the-punto), in genere 'solleva' le operazioni in una Monade anziché prendere i valori in uscita da per applicare le funzioni pure ai valori monadici.

Accade così che Monad s (in teoria) siano un tipo specifico di Functor. Functor descrive la classe di tipi che rappresentano i mapping di oggetti e operazioni in un contesto diverso. Un tipo di dati che è un'istanza di Functor esegue il mapping di oggetti nel relativo contesto tramite i relativi costruttori di dati e esegue il mapping delle operazioni nel relativo contesto tramite la funzione fmap. Per implementare un vero funtore, fmap deve funzionare in modo tale che il sollevamento della funzione di identità nel contesto del functor non modifichi i valori nel contesto del functor e sollevando due funzioni composte insieme si ottiene la stessa operazione all'interno del contesto del functor come sollevamento delle funzioni separatamente e quindi componendoli nel contesto del functor.

Molti, molti tipi di dati Haskell naturalmente formano funtori, e fmap fornisce un'interfaccia universale per sollevare le funzioni in modo che esse si applicano 'uniforme' in tutto il data functorized senza preoccuparsi della forma della particolare Functor istanza.Un paio di grandi esempi di questo sono il tipo di lista e il tipo Maybe; fmap di una funzione in un contesto elenco è esattamente la stessa operazione familiare map sugli elenchi e fmap di una funzione in un contesto Maybe applicherà normalmente la funzione per un valore Just a e non farà nulla per un valore Nothing, consentendo di eseguire operazioni su di esso senza preoccuparsi di ciò che è.

Detto tutto questo, per un capriccio della storia del Preludio Haskell attualmente non richiede Monad casi di avere anche un esempio Functor, così Monad fornisce una famiglia di funzioni che sollevano anche le operazioni in contesti monadici. L'operazione liftM fa la stessa cosa che fa fmap per le istanze Monad che sono anche istanze Functor (come dovrebbero essere). Ma fmap e liftM sollevano solo le funzioni a argomento singolo. Monad fornisce utilmente una famiglia di funzioni liftM2 - liftM5 che elevano le funzioni a più argomenti in contesto monadico allo stesso modo.

Infine, è chiesto di liftIO, che porta nella relativa idea di monade trasformatori, in cui più Monad istanze sono combinati in un unico tipo di dati applicando mappature monade valori già monadici, formando una sorta di impilare di mappature monadiche su un tipo di base puro. La libreria mtl fornisce un'implementazione di questa idea generale e nel suo modulo Control.Monad.Trans definisce due classi, MonadTrans t e Monad m => MonadIO m. La classe MonadTrans fornisce una singola funzione, lift, che consente l'accesso alle operazioni nel successivo "livello" monadico superiore nello stack, ovvero (MonadTrans t, Monad m) => m a -> t m a. La classe MonadIO fornisce una singola funzione, liftIO, che fornisce l'accesso alle operazioni di monad IO da qualsiasi "livello" nello stack, ad esempio IO a -> m a. Ciò rende il lavoro con stack di trasformatori monad molto più conveniente al costo di dover fornire molte dichiarazioni di istanze del trasformatore quando nuove istanze Monad vengono introdotte in una pila.

+0

Grazie per la tua spiegazione dettagliata! Mi sembra di capire un po 'di più le monadi e i funtori (non che li abbia mai capiti prima). – user2548080

+3

Parte del motivo per cui rispondo alle domande qui è di contribuire a rafforzare questi concetti nella mia mente in modo che vengano più naturali durante la programmazione! Dopo aver pensato a come le diverse classi di tipi lavorano insieme, il codice Haskell opaco diventa improvvisamente trasparente. In bocca al lupo! –