Is there a better way to implement a multi-channel Writer monad in Haskell?
For the record, it is possible to stack two WriterT
's on top of each other:
{-# 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
The output of the Writer
monad needs to be a Monoid
, but luckily tuples of monoids are monoids too! So this works:
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