Как я могу рефакторинг это без IORefs? - PullRequest
1 голос
/ 29 ноября 2011

Как я могу реорганизовать это так, чтобы в конечном итоге 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()

Ответы [ 2 ]

2 голосов
/ 29 ноября 2011

Опираясь на предыдущий комментарий относительно переполнения стека.

Аккумуляторы 'f' и 'c' в случае IORef или foldM должны быть оценены, чтобы предотвратить выделение длинной цепочки блоковвы повторяетеОдин из способов принудительной оценки 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 ()
2 голосов
/ 29 ноября 2011

Что не так с использованием IORefs здесь? Вы в любом случае в 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

Итак, вопрос в следующем: что мы поместим в ??? место? Нам нужно определить какой-то способ «выполнить» действие ввода-вывода, получить его результат и каким-либо образом изменить состояние с этим результатом. Нам также нужно знать, сколько раз это сделать.

 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)

Это не самое чистое решение, но у вас есть общее представление? Вы можете заменить IORefs, написав функцию, которая рекурсивно вызывает себя, передавая дополнительные параметры для отслеживания состояния. Точно такая же идея воплощена в предложении foldM , предложенном по аналогичному вопросу.

Схемы взрыва, как предполагает Натан Хауэлл, были бы мудрыми, чтобы не создавать большого толпа в succ (succ (succ ...))) в вашем штате:

mkNewState (!c, !f) val = ...
...