没有使用‘ neld’(monadwriter [log] io)的实例。
考虑此玩具练习 writer
和writert :我们需要根据预定义的一组规则过滤数据包列表。我们还需要根据另一套规则来记录一些数据包。现在考虑两个增强功能:
- 如果重复连续数据包(符合日志记录标准),我们只能进行1个日志条目,并打印重复计数。 (目标是教授所谓的“延迟记录”技巧。)
- 我们需要到每个日志条目。 (ie使用
writert w io
)
我已经实现了1个,但陷入了将其扩展到2。首先,以下是1的代码。数据包,但是将潜在的日志条目传递到下一步,该步骤将决定是打印还是合并它:
import Control.Monad
import Data.List
import Control.Monad.Writer
type Packet = Int
data Log = Log {
packet :: Packet,
acceptance :: Bool,
bulk :: Int
} deriving Show
instance Eq Log where
(==) a b = packet a == packet b
incr :: Log -> Log
incr x = x {bulk = 1 + bulk x}
shouldAccept :: Packet -> Bool
shouldAccept = even
shouldLog :: Packet -> Bool
shouldLog p = p `mod` 4 < 2
type WriterP = Writer [Log] [Packet]
merge :: (WriterP, [Log]) -> Packet -> (WriterP, [Log])
merge (prevWriter,prevLog) p = (newWriter,curLogFinal) where
acc = shouldAccept p
curLog = [Log p acc 1 | shouldLog p]
curLogFinal = if null prevLog || prevLog /= curLog then curLog else incr <$> prevLog
shouldTell = not (null prevLog) && prevLog /= curLog
newWriter = do
packets <- prevWriter
when shouldTell $ tell prevLog
return $ [p | acc] ++ packets
processPackets :: [Packet] -> WriterP
processPackets packets = fst $ foldl' merge (return [],[]) packets
main :: IO ()
main = do
let packets = [1,2,3,4,4,4,5,5,6,6] -- Ideally, read from a file
(result,logged) = runWriter $ processPackets packets
accepted = reverse result
putStrLn "ACCEPTED PACKETS"
forM_ accepted print
putStrLn "\nFIREWALL LOG"
forM_ logged print
对于2,我最初想到将我们的待处理日志输入writer
的计算。 Writert [log] IO([packet],[log])
之类的东西。但是,我不喜欢它,因为这两个增强功能原则上是无关的,如果日志与计算混合在一起,为什么还要使用单子呢?
然后,我(天真地)试图用io
将整个(writerp,[log])
包裹。当我继续修复类型错误(HAHA)时,事情似乎可以解决自己,但是随后我击中了(Monadwriter [log] io) Rocblock的没有实例。 (请参阅下面的代码。)那是什么?一些自定义的实例化可以帮助,还是这条路是死胡同?
data Log = Log {
-- ...
timestamp :: UTCTime
} deriving Show
type WriterPT = WriterT [Log] IO [Packet]
merge :: IO (WriterPT, [Log]) -> Packet -> IO (WriterPT, [Log])
merge prevWriter_prevLog p = do
t <- getCurrentTime
(prevWriter,prevLog) <- prevWriter_prevLog
let
acc = shouldAccept p
curLog = [Log p acc 1 t | shouldLog p]
curLogFinal = if null prevLog || prevLog /= curLog then curLog else incr <$> prevLog
shouldTell = not (null prevLog) && prevLog /= curLog
newWriter = do
packets <- prevWriter
lift $ when shouldTell $ tell prevLog -- Error: No instance for (MonadWriter [Log] IO)
return $ [p | acc] ++ packets
return (newWriter,curLogFinal)
processPacketsMerged :: [Packet] -> IO WriterPT
processPacketsMerged packets = fst <$> foldl' merge (return (return [],[])) packets
诚然,这很丑陋,更是如此,因为我在io
中嵌套Writert
。
所以..添加时间戳功能有哪些整洁的方法,
- 而我的第一个代码片段几乎没有更改?
- 否则?
也欢迎其他言论:)
Consider this toy exercise on Writer
and WriterT
: We need to filter a list of packets based on a predefined set of rules. We also need to log some packets based on another set of rules. Now consider two enhancements:
- In case of duplicate consecutive packets (that meet the logging criteria), we should only make 1 log entry, along with printing the duplication count. (The goal is to teach the so called 'delayed logging' trick.)
- We need to attach timestamp to every log entry. (i.e. Use
WriterT w IO
)
I have implemented 1, but am stuck in extending it to 2. Firstly, below is the code for 1. The merge
function processes the current packet, but passes on the potential log entry to the next step which will decide whether to print it or merge it:
import Control.Monad
import Data.List
import Control.Monad.Writer
type Packet = Int
data Log = Log {
packet :: Packet,
acceptance :: Bool,
bulk :: Int
} deriving Show
instance Eq Log where
(==) a b = packet a == packet b
incr :: Log -> Log
incr x = x {bulk = 1 + bulk x}
shouldAccept :: Packet -> Bool
shouldAccept = even
shouldLog :: Packet -> Bool
shouldLog p = p `mod` 4 < 2
type WriterP = Writer [Log] [Packet]
merge :: (WriterP, [Log]) -> Packet -> (WriterP, [Log])
merge (prevWriter,prevLog) p = (newWriter,curLogFinal) where
acc = shouldAccept p
curLog = [Log p acc 1 | shouldLog p]
curLogFinal = if null prevLog || prevLog /= curLog then curLog else incr <gt; prevLog
shouldTell = not (null prevLog) && prevLog /= curLog
newWriter = do
packets <- prevWriter
when shouldTell $ tell prevLog
return $ [p | acc] ++ packets
processPackets :: [Packet] -> WriterP
processPackets packets = fst $ foldl' merge (return [],[]) packets
main :: IO ()
main = do
let packets = [1,2,3,4,4,4,5,5,6,6] -- Ideally, read from a file
(result,logged) = runWriter $ processPackets packets
accepted = reverse result
putStrLn "ACCEPTED PACKETS"
forM_ accepted print
putStrLn "\nFIREWALL LOG"
forM_ logged print
For 2, initially I thought of making our pending log entry the part of Writer
's computation. Something like WriterT [Log] IO ([Packet],[Log])
. However I don't like it because the two enhancements are in principle unrelated, and if logs are to mix with computation, why use the monad at all?
Then I (naively) tried to wrap the whole (WriterP, [Log])
with IO
. Things seemed to resolve themselves as I went on fixing type errors (haha), but then I hit this No instance for (MonadWriter [Log] IO)
roadblock. (See the code below.) What is that? Can some custom instantiation help, or is this path a dead end?
data Log = Log {
-- ...
timestamp :: UTCTime
} deriving Show
type WriterPT = WriterT [Log] IO [Packet]
merge :: IO (WriterPT, [Log]) -> Packet -> IO (WriterPT, [Log])
merge prevWriter_prevLog p = do
t <- getCurrentTime
(prevWriter,prevLog) <- prevWriter_prevLog
let
acc = shouldAccept p
curLog = [Log p acc 1 t | shouldLog p]
curLogFinal = if null prevLog || prevLog /= curLog then curLog else incr <gt; prevLog
shouldTell = not (null prevLog) && prevLog /= curLog
newWriter = do
packets <- prevWriter
lift $ when shouldTell $ tell prevLog -- Error: No instance for (MonadWriter [Log] IO)
return $ [p | acc] ++ packets
return (newWriter,curLogFinal)
processPacketsMerged :: [Packet] -> IO WriterPT
processPacketsMerged packets = fst <gt; foldl' merge (return (return [],[])) packets
Admittedly it's ugly, more so because I am nesting WriterT
within IO
.
So.. what are some neat ways to add the timestamp feature,
- with little changes to my first code snippet?
- otherwise?
Other remarks are also welcome :)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
查看类型可能有很大的帮助:在您的第二个代码段中,在函数
Merge
,newwriter
应该具有类型Writert [log] io [packet]
。问题在于
lift $何时应该tell $ nell prevlog
line:您要实现的行为是记录上一个日志,如果syredtell
是正确的。如果您尝试在没有任何辅助功能的情况下编写此内容,则最终可能会出现看起来像这样的代码:现在让我们看一下如何实现时
的方式,以及是否可以使用它来重写此代码更好的方法:
这正是我们要做的,它会自动默认为默认操作,以防条件是错误的。
因此,我们可以将代码更改为:
无需使用
lift
,因为类型已经正确,因此丢失实例的错误现在消失了。要调试这种错误,让类型的检查器通过使用键入孔帮助您真的很有用:
但是,您要执行的操作(即
nell delllog
)不是> io
操作。至少这有助于我了解LIFT
不是必需的,并且您可以在时简单地使用。我希望这可以帮助!
Looking at the types can be of great help: in your second code snippet, in the function
merge
,newWriter
should have typeWriterT [Log] IO [Packet]
.The problem lies in the
lift $ when shouldTell $ tell prevLog
line: the behaviour you want to achieve is to log the previous log iffshouldTell
is true. If you try to write this without any helper function you may end up with code that looks like this:Now let's look at how
when
is implemented and if it can be used to rewrite this piece of code in a nicer way:It is exactly what we were trying to do, it automatically defaults to a default action in case the condition is false.
So we can change the code to:
No need to use
lift
as the types are already correct, the error about the missing instance is gone now.To debug this kind of error it can be really useful to let the type checker help you by using typed holes:
However, the action you want to perform (that is
tell prevLog
) is not anIO
action. At least this is what helped me understand thatlift
was not necessary and you could simply usewhen
. I hope this can help!