1

我怎样才能重构它,以便最终不需要 IORefs?

inc :: IORef Int -> IO ()
inc ref = modifyIORef ref (+1)

main = withSocketsDo $ do
        s <- socket AF_INET Datagram defaultProtocol
        c <- newIORef 0
        f <- newIORef 0
        hostAddr <- inet_addr host
        time $ forM [0 .. 10000] $ \i -> do
              sendAllTo s (B.pack  "ping") (SockAddrInet port hostAddr)
              (r, _) <- recvFrom s 1024 
              if (B.unpack r) == "PING" then (inc c) else (inc f)
        c' <- readIORef c
        print (c')
        sClose s
        return()
4

2 回答 2

2

在这里使用 IORefs 有什么问题?无论如何,您都在 IO 中进行网络操作。IORefs 并不总是最干净的解决方案,但在这种情况下它们似乎做得很好。

无论如何,为了回答这个问题,让我们删除 IORefs。这些引用用作保持状态的一种方式,因此我们必须想出另一种方式来保持状态信息。

我们想要做的伪代码是这样的:

open the connection
10000 times: 
  send a message
  receive the response
  (keep track of how many responses are the message "PING")
print how many responses were the message "PING"

缩进的块1000 times可以抽象成它自己的函数。如果我们要避免 IORefs,那么这个函数将不得不接受前一个状态并产生下一个状态。

main = withSocketsDo $ do
  s <- socket AF_INET Datagram defaultProtocol
  hostAddr <- inet_addr host
  let sendMsg = sendAllTo s (B.pack  "ping") (SockAddrInet port hostAddr)
      recvMsg = fst `fmap` recvFrom s 1024
  (c,f) <- ???
  print c
  sClose s

所以问题是这样的:我们在这个???地方放什么?我们需要定义某种方式来“执行”一个 IO 操作,获取其结果,并以某种方式使用该结果修改状态。我们还需要知道做多少次。

 performRepeatedlyWithState :: a             -- some state
                            -> IO b          -- some IO action that yields a value
                            -> (a -> b -> a) -- some way to produce a new state
                            -> Int           -- how many times to do it
                            -> IO a          -- the resultant state, as an IO action
 performRepeatedlyWithState s _ _ 0 = return s
 performRepeatedlyWithState someState someAction produceNewState timesToDoIt = do
   actionresult <- someAction
   let newState = produceNewState someState actionResult
   doWithState newState someAction produceNewState (pred timesToDoIt)

我在这里所做的只是写下与我上面所说的匹配的类型签名,并产生了相对明显的实现。我给所有东西起了一个非常冗长的名字,希望能清楚地说明这个函数的含义。配备了这个简单的功能,我们只需要使用它。

let origState = (0,0)
    action = ???
    mkNewState = ???
    times = 10000
(c,f) <- performRepeatedlyWithState origState action mkNewState times

我在这里填写了简单的参数。原始状态是(c,f) = (0,0),我们要执行 10000 次。(或者是 10001?)但是应该是什么action样子mkNewStateaction应该有IO b类型;是一些 IO 动作产生了一些东西

action = sendMsg >> recvMsg

我之前绑定了你的代码sendMsgrecvMsg的表达式。我们要执行的操作是发送一条消息,然后接收一条消息。此操作产生的值是收到的消息。

现在,应该是什么mkNewState样子?它应该有 type a -> b -> a,其中a是 Stateb的类型,是 action result 的类型。

mkNewState (c,f) val = if (B.unpack val) == "PING"
                         then (succ c, f)
                         else (c, succ f)

这不是最干净的解决方案,但你明白了吗?您可以通过编写一个递归调用自身的函数来替换 IORefs,同时传递额外的参数以跟踪状态。完全相同的想法体现在类似问题上建议的foldM 解决方案中。

succ (succ (succ ...)))正如 Nathan Howell 所建议的那样,Bang 模式是明智的,以避免在您所在的州建立大量的 thunk :

mkNewState (!c, !f) val = ...
于 2011-11-29T07:32:57.120 回答
2

基于先前关于堆栈溢出的评论。

需要评估 IORef 或 foldM 情况下的累加器“f”和“c”,以防止在迭代时分配长链 thunk。强制评估 thunk 的一种方法是使用 bang 模式。这告诉编译器评估值,删除 thunk,即使函数中不需要它的值。

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}

import Control.Concurrent
import Control.Monad
import Data.ByteString.Char8
import Data.Foldable (foldlM)
import Data.IORef
import Network.Socket hiding (recvFrom)
import Network.Socket.ByteString (recvFrom, sendAllTo)

main = withSocketsDo $ do
  let host = "127.0.0.1"
      port= 9898

  s <- socket AF_INET Datagram defaultProtocol
  hostAddr <- inet_addr host
  -- explicitly mark both accumulators as strict using bang patterns
  let step (!c, !f) i = do
        sendAllTo s "PING" (SockAddrInet port hostAddr)
        (r, _) <- recvFrom s 1024 
        return $ case r of
          -- because c and f are never used, the addition operator below
          -- builds a thunk chain. these can lead to a stack overflow
          -- when the chain is being evalulated by the 'print c' call below.
          "PING" -> (c+1, f)
          _      -> (c, f+1)
  (c, f) <- foldlM step (0, 0) [0..10000]
  print c
  sClose s
  return ()
于 2011-11-29T18:04:31.993 回答