我怎么能重构这个以便最终不需要IORef?
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()
答案 0 :(得分:2)
在这里使用IORefs有什么问题?无论如何,你都在IO中进行网络操作。 IORef并不总是最干净的解决方案,但在这种情况下他们似乎做得很好。
无论如何,为了回答这个问题,让我们删除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
下缩进的块可以被抽象为自己的函数。如果我们要避免使用IORef,那么这个函数将不得不接受先前的状态并产生下一个状态。
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
和mkNewState
应该是什么样的? action
应该有IO b
类型;这是一些产生某事的IO动作。
action = sendMsg >> recvMsg
我先前将sendMsg
和recvMsg
绑定到代码中的表达式。我们要执行的操作是发送消息,然后接收消息。此操作产生的值是收到的消息。
现在,mkNewState
应该是什么样的?它的类型应为a -> b -> a
,其中a
是州的类型,b
是操作结果的类型。
mkNewState (c,f) val = if (B.unpack val) == "PING"
then (succ c, f)
else (c, succ f)
这不是最干净的解决方案,但你能得到一般的想法吗?您可以通过编写递归调用自身的函数来替换IORef,传递额外的参数以跟踪状态。完全相同的想法体现在类似问题上提出的foldM solution中。
正如内森豪威尔所暗示的那样,Bang模式是明智的,可以避免在你所在州建立大量的succ (succ (succ ...)))
:
mkNewState (!c, !f) val = ...
答案 1 :(得分:2)
以早先关于堆栈溢出的评论为基础。
需要对IORef或foldM情况下的累加器'f'和'c'进行求值,以防止在迭代时分配长链的thunk。强制评估thunks的一种方法是使用爆炸模式。这告诉编译器评估值,删除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 ()