[英]Haskell laziness - how do I force the IO to happen sooner?
我剛開始學習Haskell。 下面是一些以強制性風格編寫的代碼,它實現了一個簡單的服務器 - 它打印出HTTP請求頭。 除了我需要在Haskell中重新思考它,使用惰性列表和更高階函數之外,我還想清楚地看到它為什么不能按照我的意圖行事。 它總是一個落后 - 我用一個請求命中它,沒有任何反應,再次點擊它,它打印第一個請求,第三次點擊它,它打印第二個請求,等等。為什么? 什么是對此代碼的最小更改,會導致它在請求進入時正確打印?
import Network
import System.IO
import Network.HTTP.Headers
acceptLoop :: Socket -> IO ()
acceptLoop s = do
(handle, hostname, _) <- accept s
putStrLn ("Accepted connection from " ++ hostname)
text <- hGetContents handle
let lns = lines text
hds = tail lns
print $ parseHeaders hds
hClose handle
acceptLoop s
main :: IO ()
main = do
s <- listenOn (PortNumber 8080)
acceptLoop s
謝謝,羅布
所有的答案都很有幫助。 下面的代碼可以工作,但不會像建議的那樣使用字節串。 一個后續問題:可以使用標准庫中的某些函數替換ioTakeWhile
,也許在Control.Monad中?
ioTakeWhile :: (a -> Bool) -> [IO a] -> IO [a]
ioTakeWhile pred actions = do
x <- head actions
if pred x
then (ioTakeWhile pred (tail actions)) >>= \xs -> return (x:xs)
else return []
acceptLoop :: Socket -> IO ()
acceptLoop s = do
(handle, hostname, _) <- accept s
putStrLn ("Accepted connection from " ++ hostname)
let lineActions = repeat (hGetLine handle)
lines <- ioTakeWhile (/= "\r") lineActions
print lines
hClose handle
您的問題是使用hGetContents
將獲取句柄上的所有內容,直到套接字關閉。 您通過嘗試解析輸入的最后一行來跟隨此調用,直到連接終止才會知道。
解決方案:獲取所需數據(或可用數據),然后終止連接。
已經太晚了,我已經累了,但這里有一個解決方案,我知道它不是最優的(讀:丑陋的罪):你可以轉到字節串 (無論如何應該這樣做)並使用hGetNonBlocking或hGetSome
而不是hGetContents
。 或者,您可以持續hGetLine
(阻止),直到解析成功滿足您的要求:
import Network
import System.IO
import Network.HTTP.Headers
import Control.Monad
import qualified Data.ByteString.Char8 as B
import Data.ByteString (hGetSome)
acceptLoop :: Socket -> IO ()
acceptLoop s = do
(handle, hostname, _) <- accept s
putStrLn ("Accepted connection from " ++ hostname)
printHeaders handle B.empty
hClose handle
where
printHeaders h s = do
t <- hGetSome h 4096
let str = B.append s t -- inefficient!
loop = printHeaders h str
case (parseHeaders . tail . lines) (B.unpack str) of
Left _ -> loop
Right x
| length x < 3 -> loop
| otherwise -> print x
main :: IO ()
main = do
hSetBuffering stdin NoBuffering
s <- listenOn (PortNumber 8080)
forever $ acceptLoop s
懶惰程序中的“控制流”與您習慣的不同。 事情不會被評估,直到它們為什么你的程序始終是輸出后面的請求。
一般來說,你可以使用“bang”操作符來制作嚴格的東西!
和BangPatterns
pragma。
如果您在這種情況下使用它(通過說!text <- hGetContents handle
),一旦請求完成,您將獲得標題的輸出。 不幸的是, hGetContents
不知道何時在print
語句之前停止等待更多數據,因為handle
未關閉。
如果您另外重構程序以在let
語句和print
之前使用hClose handle
,那么程序的行為就像您想要的那樣。
在另一種情況下,不評估print
因為text
的值永遠不會通過關閉handle
“完成”。 因為它是“懶惰的”,然后print
等待hds
和lns
,它們依次等待text
,等待hClose
...這就是為什么你得到了奇怪的行為; 在下一個請求需要套接字之前,沒有評估hClose
,這就是為什么在此之前沒有輸出的原因。
請注意,簡單地使text
嚴格仍然會永遠阻止程序,讓它“等待”文件關閉。 但是,如果文件在text
非嚴格時關閉,它將始終為空,並導致錯誤。 同時使用它們將獲得所需的效果。
進行了三項更改:我添加了{-# LANGUAGE BangPatterns #-}
pragma, text
前面的單個字符( !
),並將hClose handle
移動了幾行。
{-# LANGUAGE BangPatterns #-}
import Network
import System.IO
import Network.HTTP.Headers
acceptLoop :: Socket -> IO ()
acceptLoop s = do
(handle, hostname, _) <- accept s
putStrLn ("Accepted connection from " ++ hostname)
!text <- hGetContents handle
hClose handle
let lns = lines text
hds = tail lns
print $ parseHeaders hds
acceptLoop s
main :: IO ()
main = do
s <- listenOn (PortNumber 8080)
acceptLoop s
要完全回避這樣的問題,您可以嘗試使用System.IO.Strict
模塊中的hGetContents
函數而不是System.IO
。
而不是在acceptLoop
顯式遞歸,我發現以下main
更加慣用:
main = do
s <- listenOn (PortNumber 8080)
sequence_ $ repeat $ acceptLoop s
這樣做,您可以從acceptLoop
刪除遞歸調用。
TomMD的解決方案forever
使用Contol.Monad
模塊,這也很好。
您應該對消息何時完成有一些概念。 您需要從片段中的輸入句柄中讀取,直到您意識到您已收到完整的消息。 然后假設之后的所有內容都是下一條消息。 消息可能不會同時出現,也可能成組出現。
例如,消息可能總是固定長度。 或以\\n\\n
終止(我相信這是HTTP請求的情況)
[我可能會回來並發布代碼以獲得此建議,但如果我不這樣做,請嘗試調整TomMD的代碼,這是朝着正確方向邁出的一步]
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.