【问题标题】:Haskell: Trapped in IO monadHaskell:陷入 IO monad
【发布时间】:2013-08-15 09:33:14
【问题描述】:

我正在尝试使用 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 找到所有类型和数据构造函数。提前致谢!

【问题讨论】:

标签: haskell io monads pure-function


【解决方案1】:

一旦进入 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 

(addition:) 正如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

随便找个更直观的(记住,(.)的优先级最高,就在函数应用的下方)

顺便说一句,&gt;&gt;= return . f 位是liftM 的实际实现方式,仅在do-notation 中;它确实显示了 fmapliftM 的等价性,因为对于任何 monad 它都应该持有:

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

【讨论】:

    【解决方案2】:

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

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

    很多很多的 Haskell 数据类型自然形成函子,fmap 提供了一个通用接口来提升函数,以便它们“均匀”地应用于整个函子化数据,而不必担心特定 Functor 实例的形式。列表类型和Maybe 类型就是这方面的两个很好的例子;将函数的fmap 放入列表上下文中与熟悉的列表上的map 操作完全相同,而将函数放入Maybe 上下文中的fmap 将正常应用该函数以获取Just a 值并执行Nothing 值没有任何意义,允许您对其执行操作而不必担心它是什么。

    说了这么多,从历史上看,Haskell Prelude 目前不需要Monad 实例也有Functor 实例,所以Monad 提供了一系列函数,也可以将操作提升到单子上下文中. liftM 操作与fmapMonad 实例所做的操作相同,而Functor 实例也是Functor 实例(应该如此)。但fmapliftM 只提升单参数功能。 Monad 提供了一系列 liftM2 -- liftM5 函数,它们以同样的方式将多参数函数提升到单子上下文中。

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

    【讨论】:

    • 感谢您的详细解释!我觉得我现在对 monads 和 functors 有了更多的了解(以前我完全不了解它们)。
    • 我在这里回答问题的部分原因是为了帮助在我的脑海中强化这些概念,以便它们在编程时更自然地出现!在你思考不同类型类如何协同工作之后,真正不透明的 Haskell 代码会突然变得透明。祝你好运!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-13
    • 2012-06-08
    • 1970-01-01
    • 2016-08-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多