有没有更好的方法在Haskell中实现多通道Writer monad?

时间:2011-09-20 18:29:31

标签: haskell monads monad-transformers

问题:

我需要在同一个Haskell monad变换器堆栈中编写不同类型的writer monad。除了使用tell编写调试消息之外,我还想用它来编写其他一些数据类型,例如数据包在其他一些环境中传输。

我已经检查了Hackage的通道化编写器monad。我希望找到的是类似于编写器的monad,它支持多种数据类型,每种数据类型代表runWriter结果中不同的“逻辑”通道。我的搜索没有发现任何事情。

解决方案尝试1:

我解决问题的第一个方法是在这些方面堆叠WriterT两次:

type Packet = B.ByteString

newtype MStack a = MStack { unMStack :: WriterT [Packet] (WriterT [String] Identity) a }
  deriving (Monad)

但是,在将MStack声明为MonadWriter [Packet]MonadWriter [String]的实例时,我遇到了问题:

instance MonadWriter [String] MStack where
  tell = Control.Monad.Writer.tell
  listen = Control.Monad.Writer.listen
  pass = Control.Monad.Writer.pass

instance MonadWriter [Packet] MStack where
  tell = lift . Control.Monad.Writer.tell
  listen = lift . Control.Monad.Writer.listen
  pass = lift . Control.Monad.Writer.pass

ghci的后续投诉:

/Users/djoyner/working/channelized-writer/Try1.hs:12:10:
    Functional dependencies conflict between instance declarations:
      instance MonadWriter [String] MStack
        -- Defined at /Users/djoyner/working/channelized-writer/Try1.hs:12:10-36
      instance MonadWriter [Packet] MStack
        -- Defined at /Users/djoyner/working/channelized-writer/Try1.hs:17:10-36
Failed, modules loaded: none.

我理解为什么这种方法无效,如此处所示,但我无法找到解决基本问题的方法,所以我完全放弃了它。

解决方案尝试2:

由于它看起来堆栈中只能有一个WriterT,我在PacketString上使用了一个包装器类型,并将该事实隐藏在实用程序函数中({ {1}},runMStacktellPacket以下)。以下是完整的解决方案:

tellDebug

是的,编译和工作!

解决方案非尝试3:

我还想到,这可能是我自己滚动的时间,也包括我的实际应用程序的变换器堆栈类型中需要出现的错误,读取器和状态monad功能。我没有尝试过这个。

问题:

虽然解决方案2有效,但还有更好的方法吗?

此外,具有可变数量通道的通道化编写器monad是否可以作为包一般实现?看起来这将是一件有用的事情,我想知道它为什么不存在。

2 个答案:

答案 0 :(得分:23)

Writer monad的输出需要是Monoid,但幸运的是,幺半群的元组也是幺半群!所以这有效:

import Control.Monad.Writer
import qualified Data.ByteString as B
import Data.Monoid

type Packet = B.ByteString

tellPacket xs = tell (xs, mempty)
tellDebug  xs = tell (mempty, xs)

myFunc :: Writer ([Packet], [String]) ()
myFunc = do
  tellDebug ["Entered myFunc"]
  tellPacket [B.pack [0..255]]
  tellDebug ["Exited myFunc"]

main = do
  let (_, (ps, ds)) = runWriter myFunc
  putStrLn $ "Will be sending " ++ (show $ length ps) ++ " packets."
  putStrLn "Debug log:"
  mapM_ putStrLn ds

答案 1 :(得分:8)

为了记录,可以将两个WriterT叠加在一起:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

import Control.Monad.Writer
import Control.Monad.Identity
import qualified Data.ByteString as B

type Packet = B.ByteString

newtype MStack a = MStack { unMStack :: WriterT [Packet] (WriterT [String] Identity) a }
  deriving (Functor, Applicative, Monad)

tellDebug = MStack . lift . Control.Monad.Writer.tell
tellPacket = MStack . Control.Monad.Writer.tell

runMStack m =
  let ((a, ps), ds) = (runIdentity . runWriterT . runWriterT . unMStack) m
  in (a, ps, ds)

myFunc = do
  tellDebug ["Entered myFunc"]
  tellPacket [B.pack [0..255]]
  tellDebug ["Exited myFunc"]

main = do
  let (_, ps, ds) = runMStack myFunc
  putStrLn $ "Will be sending " ++ (show $ length ps) ++ " packets."
  putStrLn "Debug log:"
  mapM_ putStrLn ds