自定义类型的Monad和MonadIO [英] Monad and MonadIO for custom type
问题描述
我的Logger类型为* -> *
,可以采用任何类型并将值记录在文件中.我正在尝试以单例方式实现此目的,以便我登录并保持相同的状态.我的代码看起来像
I have a Logger type of kind * -> *
which can take any type and log the value in a file. I am trying to implement this in a monadic way so that I log and keep working the same. My code looks like
import Control.Applicative
import Control.Monad
import System.IO
import Control.Monad.IO.Class
instance Functor Logger where
fmap = liftM
instance Applicative Logger where
pure = return
(<*>) = ap
newtype Logger a = Logger a deriving (Show)
instance Monad (Logger) where
return = Logger
Logger logStr >>= f = f logStr
instance MonadIO (Logger) where
liftIO a = do
b <- liftIO a
return b
logContent :: (Show a) => a -> Logger a
logContent a = do
b <- liftIO $ logContent2 a
return b
logContent2 :: (Show a) => a -> IO a
logContent2 a = do
fHandle <- openFile "test.log" AppendMode
hPrint fHandle a
hClose fHandle
return (a)
liftIO函数在调用自身时会不断循环.我也无法做到b <-a.有人可以帮助正确实现MonadIO实施吗?
The liftIO function goes on endless loop as it calls itself. I am not able to do b <- a either. Can someone help on getting MonadIO implementation right ?
推荐答案
如评论中所述,我认为您误解了MonadIO
和liftIO
的作用.
As noted in the comments, I think you've misunderstood what MonadIO
and liftIO
do.
这些类型类和函数来自mtl
库.不幸的是,mtl
代表"monad转换器库",但是
These typeclasses and functions come from mtl
library. Rather unfortunately, mtl
stands for "monad transformer library", but mtl is not a monad transformer library. Rather, mtl
is a set of typeclasses that allow you to take a monad that --- and this is important --- already has a particular type of functionality and provide that monad with a consistent interface around that functionality. This ends up being really useful for working with actual monad transformers. That's because mtl
allows you to use tell
and ask
and put
to access the Writer
, Reader
, and State
functionality of your monad transformer stack in a consistent way.
与此变压器业务分开,如果您已经有一个自定义monad,例如说它支持任意IO并且具有State
功能,则可以定义一个MonadState
实例以进行标准状态操作(state
,get
,gets
,put
,modify
)可用于您的自定义monad,并且您可以定义MonadIO
实例,以允许使用liftIO
在您的自定义monad中执行任意IO操作.但是,这些类型类都不具备向Monad尚不具备的功能添加功能的能力.特别是,您不能使用MonadIO
实例将任意单子动作m a
转换为IO a
.
Separately from this transformer business, if you already have a custom monad, say that supports arbitrary IO and has State
functionality, then you can define a MonadState
instance to make the standard state operations (state
, get
, gets
, put
, modify
) available for your custom monad, and you can define a MonadIO
instance to allow an arbitrary IO action to be executed in your custom monad using liftIO
. However, none of these typeclasses are capable of adding functionality to a monad that it doesn't already have. In particular, you can't transform an arbitrary monadic action m a
into an IO a
using a MonadIO
instance.
请注意,transformers
程序包包含的类型是,可以为尚不具备的monad添加功能(例如,添加读取器或写入器功能),但是没有转换器将IO
添加到任意monad.这样的变压器将是不可能的(没有不安全或不终止的操作).
Note that the transformers
package contains types that are capable of adding functionality to a monad that it doesn't already have (e.g., adding reader or writer functionality), but there is no transformer to add IO
to an arbitrary monad. Such a transformer would be impossible (without unsafe or nonterminating operations).
还请注意,liftIO :: MonadIO m => IO a -> m a
的签名在m
上施加了MonadIO
约束,而这不仅仅是一个琐碎的约束.它实际上表明liftIO
仅适用于已经具有IO功能的monads m
,因此m
是 IO monad,或者它是具有IO
在其基础上.您的Logger
示例不具有IO功能,因此不能具有(明智的)MonadIO
实例.
Also note that the signature for liftIO :: MonadIO m => IO a -> m a
puts a MonadIO
constraint on m
, and this isn't just a trivial constraint. It actually indicates that liftIO
only works for monads m
that already have IO functionality, so either m
is the IO monad, or it's a monad stack with IO
at its base. Your Logger
example doesn't have IO functionality and so can't have a (sensible) MonadIO
instance.
回到您的特定问题上来,实际上在不完全知道您要做什么的情况下将您引导到这里实际上有点困难.如果您只想将基于文件的日志记录添加到现有的IO计算中,则定义新的转换器堆栈可能会达到目的:
Getting back to your specific problem, it's actually a little bit hard to steer you right here without knowing exactly what you're trying to do. If you just want to add file-based logging to an existing IO computation, then defining a new transformer stack will probably do the trick:
type LogIO = ReaderT Handle IO
logger :: (Show a) => a -> LogIO ()
logger a = do
h <- ask
liftIO $ hPrint h a
runLogIO :: LogIO a -> FilePath -> IO a
runLogIO act fp = withFile fp AppendMode $ \h -> runReaderT act h
您可以编写如下内容:
main :: IO ()
main = runLogIO start "test.log"
start :: LogIO ()
start = do
logger "Starting program"
liftIO . putStrLn $ "Please enter your name:"
n <- liftIO $ getLine
logger n
liftIO . putStrLn $ "Hello, " ++ n
logger "Ending program"
在LogIO
monad中使用IO操作时需要添加liftIO
调用是很丑陋的,但在很大程度上是不可避免的.
The need to add liftIO
calls when using IO actions within the LogIO
monad is ugly but largely unavoidable.
该解决方案还可以将基于文件的日志记录添加到纯计算中,但要理解,如果要安全地记录到文件,则无论如何都必须将它们转换为IO计算.
This solution would also work for adding file-based logging to pure computations, with the understanding that you have to convert them to IO computations anyway if you want to safely log to a file.
更通用的解决方案是定义您自己的monad transformer (不仅仅是您自己的monad),例如LoggerT m
,以及关联的MonadLogger
类型类,该类将添加基于文件的日志记录到任何具有IO功能的monad堆栈.这个想法是,然后您可以创建任意的自定义monad堆栈:
The more general solution is to define your own monad transformer (not merely your own monad), like LoggerT m
, together with an associated MonadLogger
type class that will add file-based logging to to any IO-capable monad stack. The idea would be that you could then create arbitrary custom monad stacks:
type MyMonad = StateT Int (LoggerT IO)
然后编写代码,以混合来自不同层的单子计算(例如混合状态计算和基于文件的日志记录):
and then write code that mixes monadic computations from different layers (like mixing state computations and file-based logging):
newSym :: String -> MyMonad String
newSym pfx = do
n <- get
logger (pfx, n)
put (n+1)
return $ pfx ++ show n
这就是您要执行的操作吗?如果没有,也许您可以在这里或在一个新问题中描述如何尝试将日志记录添加到一些示例代码中.
Is this what you what you're trying to do? If not, maybe you could describe, either here or in a new question, how you're trying to add logging to some example code.
这篇关于自定义类型的Monad和MonadIO的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!