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

スタックオーバーフローに関する以前のコメントに基づいています。

IORef または foldM のいずれかの場合のアキュムレータ 'f' および 'c' を評価して、反復中にサンクの長いチェーンが割り当てられるのを防ぐ必要があります。サンクの評価を強制する 1 つの方法は、bang パターンを使用することです。これは、値が関数で要求されていない場合でも、値を評価してサンクを削除するようにコンパイラに指示します。

{-# 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 に答える
2

ここで 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 ですか?) しかし、どのようactionmkNewState見えるべきでしょうか? にはactiontype が必要IO bです。何かを生成するのは IO アクションです。

action = sendMsg >> recvMsg

前にあなたのコードの式にsendMsgandをバインドしました。recvMsg実行したいアクションは、メッセージを送信してからメッセージを受信することです。このアクションが生成する値は、受信したメッセージです。

さて、どのmkNewStateように見えるべきですか?ここで、 は Stateのタイプa -> b -> a、はアクションの結果のタイプです。ab

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 = ...
于 2011-11-29T07:32:57.120 に答える