哈斯克尔:陷入IO monad

时间:2013-08-13 16:41:31

标签: haskell io

我正在尝试使用haskell-src-exts包中的parseFile函数来解析文件。我正在尝试使用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 

我只想在parseFile的输出上使用pMod'功能。请注意,如果有帮助,可以在http://hackage.haskell.org/packages/archive/haskell-src-exts/1.13.5/doc/html/Language-Haskell-Exts-Syntax.html找到所有类型和数据构造函数。提前致谢!

2 个答案:

答案 0 :(得分: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 

添加 :)正如great answer by 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 . f位是liftM实际实现的方式,仅在do - 表示法中;并且它确实显示了fmapliftM的等价性,因为对于任何monad它应该保持:

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

答案 1 :(得分:15)

为了给出一个比威尔斯更为一般的答案(这当然是正确的和非常重要的),你通常会解除&#39;操作进入 Monad而不是取值 out ,以便将纯函数应用于monadic值。

恰好Monad s(理论上)是Functor的特定种类。 Functor描述了表示对象和操作到不同上下文的映射的类型类。作为Functor实例的数据类型通过其数据构造函数将对象映射到其上下文中,并通过fmap函数将操作映射到其上下文中。要实现真正的仿函数,fmap必须以这样的方式工作:将标识函数提升到仿函数上下文不会更改仿函数上下文中的值,并且提升组合在一起的两个函数会在仿函数上下文中生成相同的操作单独提升函数,然后在函子上下文中组合它们。

许多Haskell数据类型自然形成仿函数,而fmap提供了一个通用接口来提升函数,以便它们均匀地应用于&#39;整个仿函数数据,而不必担心特定Functor实例的形式。一些很好的例子是列表类型和Maybe类型;列表上下文中的函数fmap与列表上熟悉的map操作完全相同,而fmap上下文中函数的Maybe将正常应用函数对于Just a值并且对Nothing值不执行任何操作,允许您对其执行操作而不必担心它是什么。

说了这么多,根据历史的怪癖,Haskell Prelude目前还不需要Monad个实例也有Functor个实例,所以Monad提供了一个功能也将操作提升到monadic环境中。操作liftMfmap对同样为Monad个实例的Functor实例执行的操作相同(应该如此)。但fmapliftM仅提升单参数函数。 Monad有助于提供一系列liftM2 - liftM5函数,以相同的方式将多参数函数提升为monadic上下文。

最后,您询问了liftIO,它引入了monad 转换器的相关概念,其中多个Monad实例通过应用monad组合在一个数据类型中映射到已经为monadic的值,形成一种基本纯类型的monadic映射堆栈 mtl 库提供了此一般概念的一个实现,并在其模块Control.Monad.Trans中定义了两个类MonadTrans tMonad m => MonadIO mMonadTrans类提供单个函数lift,该函数允许访问下一个更高的monadic&#34;层中的操作。在堆栈中,即(MonadTrans t, Monad m) => m a -> t m aMonadIO类提供单个函数liftIO,可以从任何&#34;层提供IO monad操作的访问权限。在堆栈中,即IO a -> m a。这些使得使用monad变换器堆栈更加方便,但是当新的Monad实例被引入堆栈时必须提供大量的变换器实例声明。

相关问题