有没有更好的方法在 Haskell 中实现多通道 Writer monad?
问题:
我需要在同一个 Haskell monad 转换器堆栈中组合不同类型的 writer monad。除了使用 tell
编写调试消息之外,我还想用它来编写其他一些数据类型,例如要在其他上下文中传输的数据包。
我检查过 Hackage 的通道化编写器单子。我希望找到一个类似 writer 的 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
,因此我在 Packet
和 Packet
上使用包装类型code>String 并将事实隐藏在实用函数中(下面的 runMStack
、tellPacket
和 tellDebug
)。这是有效的完整解决方案:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Control.Monad.Identity
import Control.Monad.Writer
import qualified Data.ByteString as B
type Packet = B.ByteString
data MStackWriterWrapper = MSWPacket Packet
| MSWDebug String
newtype MStack a = MStack { unMStack :: WriterT [MStackWriterWrapper] Identity a }
deriving (Monad, MonadWriter [MStackWriterWrapper])
runMStack :: MStack a -> (a, [Packet], [String])
runMStack act = (a, concatMap unwrapPacket ws, concatMap unwrapDebug ws)
where (a, ws) = runIdentity $ runWriterT $ unMStack act
unwrapPacket w = case w of
MSWPacket p -> [p]
_ -> []
unwrapDebug w = case w of
MSWDebug d -> [d]
_ -> []
tellPacket = tell . map MSWPacket
tellDebug = tell . map MSWDebug
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
是的,编译并运行!
解决方案非尝试 3:
我还想到,这可能是我推出自己的解决方案的时候,还包括需要出现在我的实际应用程序中的错误、读取器和状态 monad 功能变压器堆型。我没有尝试这样做。
问题:
虽然解决方案2有效,但是还有更好的方法吗?
另外,具有可变数量通道的通道化编写器 monad 是否可以普遍实现为一个包?这似乎是一件有用的事情,但我想知道为什么它还不存在。
Problem:
I need to compose writer monads of different types in the same Haskell monad transformer stack. Besides using tell
to write debug messages I'd also like to use it to write some other data type, e.g. data packets to be transmitted in some other context.
I've checked Hackage for a channelized writer monad. What I was hoping to find was a writer-like monad that supports multiple data types, each representing a distinct "logical" channel in the runWriter
result. My searches didn't turn up anything.
Solution Attempt 1:
My first approach at solving the problem was to stack WriterT
twice along these lines:
type Packet = B.ByteString
newtype MStack a = MStack { unMStack :: WriterT [Packet] (WriterT [String] Identity) a }
deriving (Monad)
However, I ran into problems when declaring MStack
as an instance of both MonadWriter [Packet]
and 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
Subsequent complaints from 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.
I understand why this approach is not valid as shown here but I couldn't figure out a way around the fundamental issues so I abandoned it altogether.
Solution Attempt 2:
Since it appears there can only be a single WriterT
in the stack, I'm using a wrapper type over Packet
and String
and hiding the fact in the utility functions (runMStack
, tellPacket
, and tellDebug
below). Here's the complete solution that does work:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Control.Monad.Identity
import Control.Monad.Writer
import qualified Data.ByteString as B
type Packet = B.ByteString
data MStackWriterWrapper = MSWPacket Packet
| MSWDebug String
newtype MStack a = MStack { unMStack :: WriterT [MStackWriterWrapper] Identity a }
deriving (Monad, MonadWriter [MStackWriterWrapper])
runMStack :: MStack a -> (a, [Packet], [String])
runMStack act = (a, concatMap unwrapPacket ws, concatMap unwrapDebug ws)
where (a, ws) = runIdentity $ runWriterT $ unMStack act
unwrapPacket w = case w of
MSWPacket p -> [p]
_ -> []
unwrapDebug w = case w of
MSWDebug d -> [d]
_ -> []
tellPacket = tell . map MSWPacket
tellDebug = tell . map MSWDebug
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
Yay, compiles and works!
Solution Non-Attempt 3:
It also occurred to me that this might be a time when I'd roll my own, also including error, reader, and state monad functionality that needs be present in my actual application's transformer stack type. I didn't attempt this.
Question:
Although solution 2 works, is there a better way?
Also, could a channelized writer monad with a variable number of channels be generically implemented as a package? It would seem like that would be a useful thing and I'm wondering why it doesn't already exist.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
Writer
monad 的输出需要是一个Monoid
,但幸运的是,monoid 的元组也是 monoid!所以这有效:The output of the
Writer
monad needs to be aMonoid
, but luckily tuples of monoids are monoids too! So this works:作为记录,可以将两个 WriterT 堆叠在一起:
For the record, it is possible to stack two
WriterT
's on top of each other: