Haskell: Trapped in IO monad

后端 未结 2 1729
梦谈多话
梦谈多话 2020-12-19 06:08

I am trying to parse a file using the parseFile function found in the the haskell-src-exts package. I am trying to work with the output of parseFile which is of course IO,

相关标签:
2条回答
  • 2020-12-19 06:34

    To give a more general answer than Will's (which was certainly correct and to-the-point), you typically 'lift' operations into a Monad rather than taking values out of them in order to apply pure functions to monadic values.

    It so happens that Monads (theoretically) are a specific kind of Functor. Functor describes the class of types that represent mappings of objects and operations into a different context. A data type that is an instance of Functor maps objects into its context via its data constructors and it maps operations into its context via the fmap function. To implement a true functor, fmap must work in such a way that lifting the identity function into the functor context does not change the values in the functor context, and lifting two functions composed together produces the same operation within the functor context as lifting the functions separately and then composing them within the functor context.

    Many, many Haskell data types naturally form functors, and fmap provides a universal interface to lift functions so that they apply 'evenly' throughout the functorized data without worrying about the form of the particular Functor instance. A couple of great examples of this are the list type and the Maybe type; fmap of a function into a list context is exactly the same as the familiar map operation on lists, and fmap of a function into a Maybe context will apply the function normally for a Just a value and do nothing for a Nothing value, allowing you to perform operations on it without worrying about which it is.

    Having said all that, by a quirk of history the Haskell Prelude doesn't currently require Monad instances to also have a Functor instance, so Monad provides a family of functions that also lift operations into monadic contexts. The operation liftM does the same thing that fmap does for Monad instances that are also Functor instances (as they should be). But fmap and liftM only lift single-argument functions. Monad helpfully provides a family of liftM2 -- liftM5 functions that lift multi-argument functions into monadic context in the same way.

    Finally, you asked about liftIO, which brings in the related idea of monad transformers, in which multiple Monad instances are combined in a single data type by applying monad mappings to already-monadic values, forming a kind of stack of monadic mappings over a basic pure type. The mtl library provides one implementation of this general idea, and in its module Control.Monad.Trans it defines two classes, MonadTrans t and Monad m => MonadIO m. The MonadTrans class provides a single function, lift, that gives access to operations in the next higher monadic "layer" in the stack, i.e. (MonadTrans t, Monad m) => m a -> t m a. The MonadIO class provides a single function, liftIO, that provides access to IO monad operations from any "layer" in the stack, i.e. IO a -> m a. These make working with monad transformer stacks much more convenient at the cost of having to provide a lot of transformer instance declarations when new Monad instances are introduced to a stack.

    0 讨论(0)
  • 2020-12-19 06:40

    Once inside IO, there's no escape.

    Use 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 
    

    (addition:) As explained in great answer by Levi Pearson, there's also

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

    But that's no black magic either. Consider:

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

    So your function can also be written as

    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
    

    whatever you find more intuitive (remember, (.) has the highest precedence, just below the function application).

    Incidentally, the >>= return . f bit is how liftM is actually implemented, only in do-notation; and it really shows the equivalency of fmap and liftM, because for any monad it should hold that:

    fmap f m = m >>= (return . f)
    
    0 讨论(0)
提交回复
热议问题