[英]Haskell: Trapped in IO monad
我正在尝试使用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找到,如果这有帮助。 提前致谢!
一旦进入 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 . f
>>= return . f
位是liftM
的实际实现方式,仅在do
表示法中; 并且它确实显示了fmap
和liftM
的等价性,因为对于任何 monad 都应该保持:
fmap f m == m >>= (return . f)
为了让比威尔的更普遍的答案(这当然正确,到了点),你通常“电梯”业务并入一个单子,而不是为了申请纯函数单子值取值了出来。
碰巧Monad
s(理论上)是一种特定类型的Functor
。 Functor
描述了表示对象和操作到不同上下文的映射的类型类。 作为Functor
实例的数据类型通过其数据构造函数将对象映射到其上下文中,并通过fmap
函数将操作映射到其上下文中。 为了实现真正的函子, fmap
必须以这样一种方式工作, fmap
恒等函数提升到函子上下文中不会改变函子上下文中的值,并且提升两个组合在一起的函数在函子上下文中产生与提升函数相同的操作分开,然后在函子上下文中组合它们。
许多、许多 Haskell 数据类型自然形成函子,而fmap
提供了一个通用接口来提升函数,以便它们“均匀地”应用于整个Functor
化数据,而无需担心特定Functor
实例的形式。 一些很好的例子是列表类型和Maybe
类型; 将函数fmap
到列表上下文中与熟悉的列表上的map
操作fmap
,将函数fmap
到Maybe
上下文中将正常应用函数对Just a
值而对Nothing
值不做任何Nothing
,允许您对其执行操作而不必担心它是哪个。
话虽如此,根据历史的怪癖,Haskell Prelude 目前不需要Monad
实例也有一个Functor
实例,因此Monad
提供了一系列函数,这些函数也将操作提升到 monadic 上下文中。 操作liftM
做同样的事情fmap
确实为Monad
实例,同时也是Functor
实例(因为他们应该)。 但是fmap
和liftM
只提升单参数函数。 Monad
提供了一系列liftM2
—— liftM5
函数,它们以相同的方式将多参数函数提升到monadic 上下文中。
最后,你问到了liftIO
,它带来了 monad转换器的相关思想,其中通过将 monad 映射应用到已经存在的 monadic 值,将多个Monad
实例组合成一个单一的数据类型,在一个基本的基础上形成一种 monadic 映射堆栈。纯类型。 mtl库提供了这一总体思想的一种实现,并且在其模块Control.Monad.Trans
定义了两个类, MonadTrans t
和Monad m => MonadIO m
。 MonadTrans
类提供了一个函数lift
,它可以访问堆栈中下一个更高的 monadic “层”中的操作,即(MonadTrans t, Monad m) => ma -> tma
。 MonadIO
类提供了一个单一的函数, liftIO
,它提供了对来自堆栈中任何“层”的IO
monad 操作的访问,即IO a -> ma
。 这些使得使用 monad 转换器堆栈更加方便,代价是在将新的Monad
实例引入堆栈时必须提供大量转换器实例声明。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.