ここで IORef を使用することの何が問題になっていますか? とにかく、ネットワーク操作でIOにいます。IORef は常に最もクリーンなソリューションとは限りませんが、この場合はうまく機能しているようです。
とにかく、質問に答えるために、IORef を削除しましょう。これらの参照は状態を維持する方法として機能するため、ステートフルな情報を維持する別の方法を考え出す必要があります。
やりたいことの擬似コードは次のとおりです。
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
type が必要IO b
です。何かを生成するのは IO アクションです。
action = sendMsg >> recvMsg
前にあなたのコードの式にsendMsg
andをバインドしました。recvMsg
実行したいアクションは、メッセージを送信してからメッセージを受信することです。このアクションが生成する値は、受信したメッセージです。
さて、どのmkNewState
ように見えるべきですか?ここで、 は Stateのタイプa -> b -> a
、はアクションの結果のタイプです。a
b
mkNewState (c,f) val = if (B.unpack val) == "PING"
then (succ c, f)
else (c, succ f)
これは最もクリーンな解決策ではありませんが、一般的な考え方はわかりましたか? 自身を再帰的に呼び出す関数を作成し、状態を追跡するために追加のパラメーターを渡すことで、IORef を置き換えることができます。同様の質問で提案されているfoldMソリューションには、まったく同じアイデアが具体化されています。
succ (succ (succ ...)))
ネイサン・ハウエルが示唆するように、あなたの状態で大きなサンクを構築することを避けるために、強打パターンは賢明です:
mkNewState (!c, !f) val = ...