7

我正在尝试使用包parseFile中的函数解析文件haskell-src-exts

我正在尝试使用其输出parseFile当然是IO,但我无法弄清楚如何绕过IO. 我找到了一个功能liftIO,但我不确定这是否是这种情况下的解决方案。这是下面的代码。

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 

我只想能够pMod'parseFile.

请注意,所有类型和数据构造函数都可以在http://hackage.haskell.org/packages/archive/haskell-src-exts/1.13.5/doc/html/Language-Haskell-Exts-Syntax.html找到,如果这有帮助。提前致谢!

4

2 回答 2

16

一旦进入 IO,就无法逃脱。

使用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 

补充:)正如Levi Pearson在很好的回答中所解释的那样,还有

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

但这也不是黑魔法。考虑:

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

所以你的函数也可以写成

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

无论发现什么更直观(请记住,(.)具有最高优先级,就在功能应用程序的下方)

顺便说一句,>>= return . fbit 是如何liftM实际实现的,只是在do-notation 中;它确实显示了 and 的等价性fmapliftM因为对于任何 monad 它都应该持有:

fmap f m  ==  m >>= (return . f)
于 2013-08-13T17:00:08.013 回答
15

为了给出比 Will 的更一般的答案(这当然是正确且中肯的),您通常会将操作“提升”Monad 中,而不是从中取出值以便将纯函数应用于 monad 值。

碰巧Monads (理论上)是一种特定的FunctorFunctor描述了表示对象和操作到不同上下文的映射的类型类。作为实例的数据类型Functor通过其数据构造函数将对象映射到其上下文中,并通过fmap函数将操作映射到其上下文中。要实现一个真正的函子,fmap必须以这样一种方式工作:将恒等函数提升到函子上下文中不会改变函子上下文中的值,并且提升两个组合在一起的函数在函子上下文中产生与分别提升函数相同的操作然后在仿函数上下文中组合它们。

许多很多 Haskell 数据类型自然形成函子,并fmap提供通用接口来提升函数,以便它们“均匀”地应用于整个函子化数据,而不必担心特定Functor实例的形式。列表类型和Maybe类型是这方面的几个很好的例子;将函数放入列表上下文与熟悉的列表操作fmap完全相同,将函数放入上下文将正常地将函数应用于值而对值不执行任何操作,从而使您可以对其执行操作而不必担心它是。mapfmapMaybeJust aNothing

说了这么多,由于历史的一个怪癖,Haskell Prelude 目前不需要Monad实例也有一个Functor实例,因此Monad提供了一系列函数,这些函数也可以将操作提升到 monadic 上下文中。该操作对同时也是实例的实例liftM执行相同的操作(因为它们应该是)。但是并且只提升单参数功能。有用地提供了一系列函数,它们以相同的方式将多参数函数提升到单子上下文中。fmapMonadFunctorfmapliftMMonadliftM2liftM5

最后,你问到了liftIO,这带来了 monad转换器的相关思想,其中通过将 monad 映射应用于已经是 monad 的值,将多个实例组合成一个数据类型,在基本纯类型上Monad形成一种 monad 映射堆栈. mtl库提供了这个一般思想的一个实现,在它的模块中Control.Monad.Trans它定义了两个类,MonadTrans tMonad m => MonadIO m. 该类MonadTrans提供了一个函数,lift,它可以访问堆栈中下一个更高的单子“层”中的操作,即(MonadTrans t, Monad m) => m a -> t m a。该类MonadIO提供了一个函数,liftIO,它提供对IO来自堆栈中任何“层”的 monad 操作,即IO a -> m a. 这些使得使用 monad 转换器堆栈更加方便,代价是在将新Monad实例引入堆栈时必须提供大量转换器实例声明。

于 2013-08-13T19:31:39.163 回答