没有使用‘ neld’(monadwriter [log] io)的实例。

发布于 2025-02-03 13:20:16 字数 3346 浏览 2 评论 0原文

考虑此玩具练习 writerwritert :我们需要根据预定义的一组规则过滤数据包列表。我们还需要根据另一套规则来记录一些数据包。现在考虑两个增强功能:

  1. 如果重复连续数据包(符合日志记录标准),我们只能进行1个日志条目,并打印重复计数。 (目标是教授所谓的“延迟记录”技巧。)
  2. 我们需要到每个日志条目。 (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:

  1. 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.)
  2. 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

赴月观长安 2025-02-10 13:20:16

查看类型可能有很大的帮助:在您的第二个代码段中,在函数Mergenewwriter应该具有类型Writert [log] io [packet]

问题在于lift $何时应该tell $ nell prevlog line:您要实现的行为是记录上一个日志,如果syredtell是正确的。如果您尝试在没有任何辅助功能的情况下编写此内容,则最终可能会出现看起来像这样的代码:

do ...
   if shouldTell
     then (tell prevLog) -- log the previous log
     else (return ())    -- do nothing   
   ...

现在让我们看一下如何实现时的方式,以及是否可以使用它来重写此代码更好的方法:

when :: (Applicative f) => Bool -> f () -> f ()
when p s  = if p then s else pure ()

这正是我们要做的,它会自动默认为默认操作,以防条件是错误的。
因此,我们可以将代码更改为:

do ...
   when shouldTell $ tell oldLog
   ...

无需使用lift,因为类型已经正确,因此丢失实例的错误现在消失了。

要调试这种错误,让类型的检查器通过使用键入孔帮助您真的很有用:

do ... 
   lift $ when shouldTell $ _ -- Found hole: _ :: IO ()
   ...

但是,您要执行的操作(即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 type WriterT [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 iff shouldTell is true. If you try to write this without any helper function you may end up with code that looks like this:

do ...
   if shouldTell
     then (tell prevLog) -- log the previous log
     else (return ())    -- do nothing   
   ...

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:

when :: (Applicative f) => Bool -> f () -> f ()
when p s  = if p then s else pure ()

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:

do ...
   when shouldTell $ tell oldLog
   ...

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:

do ... 
   lift $ when shouldTell $ _ -- Found hole: _ :: IO ()
   ...

However, the action you want to perform (that is tell prevLog) is not an IO action. At least this is what helped me understand that lift was not necessary and you could simply use when. I hope this can help!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文