在这里使用 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
样子mkNewState
?action
应该有IO b
类型;是一些 IO 动作产生了一些东西。
action = sendMsg >> recvMsg
我之前绑定了你的代码sendMsg
中recvMsg
的表达式。我们要执行的操作是发送一条消息,然后接收一条消息。此操作产生的值是收到的消息。
现在,应该是什么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 = ...