Haskell懒惰 - 我如何更快地强制IO发生?

时间:2011-03-21 04:43:29

标签: haskell io lazy-evaluation

我刚开始学习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

3 个答案:

答案 0 :(得分:10)

您的问题是使用hGetContents将获取句柄上的所有内容,直到套接字关闭。您可以通过尝试解析输入的最后一行来跟随此调用,直到连接终止时才会知道该行。

解决方案:获取所需数据(或可用数据),然后终止连接。

已经很晚了,我已经累了但这里有一个解决方案,我知道它不是最优的(读作:丑陋的罪):你可以转到字节串(无论如何应该这样做)并使用hGetNonBlocking或{{1而不是hGetSome。或者,您可以hGetContents(阻止),直到解析成功满足您的要求:

hGetLine

答案 1 :(得分:6)

该方法的简要概述:

懒惰程序中的“控制流程”与您习惯的不同。事情将不会被评估,直到它们为什么你的程序始终是输出后面的请求。

一般情况下,您可以使用“bang”运算符!BangPatterns pragma来制定严格的内容。

如果您在这种情况下使用它(通过说!text <- hGetContents handle),一旦请求完成,您将获得标题的输出。遗憾的是,hGetContents不知道何时在print语句之前停止等待更多数据,因为handle未关闭。

如果您另外重构程序,使hClose handle {/ 1}}语句和let之前的print ,那么程序的行为就像您想要的那样。< / p>

在另一种情况下,print未被评估,因为text的值在handle关闭时永远不会“完成”。由于它是“懒惰”,print正等待hdslns,而text又等待hClose,等待hClose。这就是为什么你得到了奇怪的行为;在下一个请求需要套接字之前,text未被评估,这就是为什么在此之前没有输出。

请注意,简单地使text严格仍然会永久阻止程序,让它“等待”文件关闭。但是,如果文件在{-# LANGUAGE BangPatterns #-}非严格时关闭,则它将始终为空,并导致错误。同时使用它们将获得所需的效果。


您的程序包含建议的更改:

进行了三项更改:我在!前面添加了text个pragma,一个字符(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 向上移了几行。< / p>

hGetContents

另一种方法:

要完全回避这类问题,您可以尝试使用System.IO.Strict模块中的System.IO功能代替acceptLoop


最后一点:

我发现以下main更加惯用,而不是main = do s <- listenOn (PortNumber 8080) sequence_ $ repeat $ acceptLoop s 中的显式递归:

acceptLoop

执行此操作,您可以从forever删除递归调用。

TomMD的解决方案使用Contol.Monad模块中的{{1}},这也很好。

答案 2 :(得分:3)

您应该对消息何时完成有一些概念。您需要从片段中的输入句柄中读取,直到您意识到您已收到完整的消息。然后假设之后的所有内容都是下一条消息。消息可能不会立即出现,也可能成组出来。

例如,

消息可能始终是固定长度。或以\n\n终止(我相信这是HTTP请求的情况)

[我可能会回来并发布代码以获得此建议,但如果我不这样做,只需尝试调整TomMD的代码,这是朝着正确方向迈出的一步]