自定义类型的Monad和MonadIO [英] Monad and MonadIO for custom type

查看:187
本文介绍了自定义类型的Monad和MonadIO的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的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 ?

推荐答案

如评论中所述,我认为您误解了MonadIOliftIO的作用.

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实例以进行标准状态操作(stategetgetsputmodify)可用于您的自定义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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆