問題:
同じ Haskell モナド トランスフォーマー スタックで異なる型のライター モナドを構成する必要があります。デバッグ メッセージの書き込みに使用tell
する以外に、他のデータ タイプ (たとえば、他のコンテキストで送信されるデータ パケット) の書き込みにも使用したいと考えています。
チャネライズド ライター モナドの Hackage を確認しました。私が見つけたいと思っていたのは、複数のデータ型をサポートするライターのようなモナドで、それぞれがrunWriter
結果の個別の「論理」チャネルを表しています。私の検索では何も見つかりませんでした。
解決策の試み 1:
問題を解決するための私の最初のアプローチは、WriterT
次の行に沿って 2 回スタックすることでした。
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:
スタックには1 つしか存在しないように見えるのでWriterT
、ラッパー タイプを使用して、ユーティリティ関数 ( 、、およびPacket
以下) でその事実を隠しています。機能する完全なソリューションは次のとおりです。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:
また、実際のアプリケーションのトランスフォーマー スタック タイプに存在する必要があるエラー、リーダー、および状態モナド機能も含めて、自分で作成する時期になるのではないかと思いました。私はこれを試みませんでした。
質問:
解決策 2 は機能しますが、より良い方法はありますか?
また、可変数のチャネルを持つチャネル化されたライター モナドは、パッケージとして一般的に実装できますか? それは便利なことのように思えますが、なぜまだ存在しないのだろうと思っています。