我正在尝试使用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
位是liftM
的实际实现方式,只是在do
表示法中;它确实显示了fmap
和liftM
的等价性,因为对于任何 monad 它都应该持有:
fmap f m == m >>= (return . f)
为了给出比 Will 更通用的答案(这当然是正确的和切中要害的(,你通常会将操作"提升"到 Monad 中,而不是从中取出值,以便将纯函数应用于一元值。
碰巧Monad
s(理论上(是一种特定的Functor
. Functor
描述了表示对象和操作到不同上下文中的映射的类型类。作为 Functor
实例的数据类型通过其数据构造函数将对象映射到其上下文中,并通过 fmap
函数将操作映射到其上下文中。要实现真正的函子,fmap
必须以这样一种方式工作:将恒等函数提升到函子上下文中不会更改函子上下文中的值,并且提升组合在一起的两个函数在函子上下文中产生与分别提升函数然后在函子上下文中组合它们相同的操作。
许多Haskell数据类型自然地形成函子,fmap
提供了一个通用接口来提升函数,以便它们在整个函化数据中"均匀"应用,而不必担心特定Functor
实例的形式。这方面的几个很好的例子是列表类型和Maybe
类型; 将函数fmap
到列表上下文中与对列表执行熟悉的map
操作完全相同,将函数fmap
到Maybe
上下文中将函数正常应用于Just a
值,而不对Nothing
值执行任何操作,从而允许您对其执行操作而不必担心它是哪个。
话虽如此,根据历史的怪癖,Haskell Prelude 目前并不要求Monad
实例也具有Functor
实例,因此Monad
提供了一系列函数,这些函数也可以将操作提升到一元上下文中。该操作liftM
执行的操作与 fmap
对Monad
实例执行的操作相同,这些实例也是Functor
实例(因为它们应该是(。但是fmap
和liftM
只能提升单参数函数。 Monad
提供了一系列liftM2
- liftM5
函数,以相同的方式将多参数函数提升到一元上下文中。
最后,您询问了 liftIO
,它引入了 monad 转换器的相关思想,其中通过将 monad 映射应用于已经 monadic 的值,将多个 Monad
实例组合在单个数据类型中,形成一种基于基本纯类型的 monadic 映射堆栈。 mtl 库提供了这个一般思想的一个实现,并在其模块Control.Monad.Trans
中定义了两个类, MonadTrans t
和 Monad m => MonadIO m
. MonadTrans
类提供了一个函数,lift
,它允许访问堆栈中下一个更高的一元"层"中的操作,即 (MonadTrans t, Monad m) => m a -> t m a
. MonadIO
类提供单个函数 liftIO
,它提供从堆栈中的任何"层"访问IO
monad 操作,即 IO a -> m a
.这使得使用 monad 变压器堆栈变得更加方便,但代价是在将新的Monad
实例引入堆栈时必须提供大量转换器实例声明。