如何不顾Haskell的懒惰输出进度信息?

How to output progress information in spite of Haskell's laziness?

今天我希望 Haskell 表现得像任何命令式语言,看看这个:

import Data.HashMap.Strict as HashMap
import Data.Text.IO
import Data.Text
import Data.Functor ((<&>))

putStr "Reading data from file ..."
ls <- lines <$> readFile myFile
putStrLn " done."

putStr "Processing data ..."
let hmap = HashMap.fromList $ ls <&> \l -> case splitOn " " l of
        [k, v] -> (k, v)
        _      -> error "expecting \"key value\""
putStrLn " done."

基本上,用户应该知道此刻程序在做什么。这段代码的结果是

的立即输出
> Reading data from file ... done.
> Sorting data ... done.

...然后它开始做实际工作,输出结果违背了它的目的。

我很清楚这是一项功能。 Haskell 是声明性的,评估顺序由实际依赖项决定,而不是由我的 .hs 文件中的行号决定。因此我尝试了以下方法:

putStr "Reading data from file ..."
lines <- lines <$> readFile myFile
putStrLn $ lines `seq` " done."

putStr "Processing data ..."
let hmap = HashMap.fromList $ ls <&> \l -> case splitOn " " l of
        [k, v] -> (k, v)
        _      -> error "expecting \"key value\""
putStrLn $ hmap `seq` " done."

想法:seq 仅 returns 一旦其第一个参数被评估为 Weak Head Normal Form。它有效,有点。我的程序的输出现在暂时没有,然后,一旦工作完成,就会发生所有 IO。

有解决办法吗?


编辑:我更改了问题以回复 Ben 的回答。导入现在应该更有意义并且程序真正运行。

DanielWagner 评论了这个相关问题:

GHCi and compiled code seem to behave differently

确实解决了我的问题。

putStrLn $ hmap `seq` " done."

做的正是它应该做的。我只是缺少冲洗标准输出。所以这实际上满足了我的需要:

putStr "Reading data from file ..."
hFlush stdout -- from System.IO
lines <- lines <$> readFile myFile
putStrLn $ lines `seq` " done."

putStr "Processing data ..."
hFlush stdout
let hmap = HashMap.fromList $ ls <&> \l -> case splitOn " " l of
        [k, v] -> (k, v)
        _      -> error "expecting \"key value\""
putStrLn $ hmap `seq` " done."

你没有给我们你所说的具有这种行为的实际代码:

The output of my program is now nothing for a while and then, once the work as been done, all the IO occurs.

我怎么知道这不是您运行宁的代码?您的代码根本无法编译为 运行!几个问题:

  1. 您从 lines 收到类型错误,因为它属于标准 Prelude,但该版本适用于 String,而您正在使用 Text
  2. 您还没有从任何地方导入 splitOn
  3. 要导入的明显 splitOn 来自 Data.Text,但其类型为 Text -> Text -> [Text],即 returns a list Text 在分隔符的所有出现处拆分。您显然期待一对,仅在第一个分隔符处拆分。

因此,在 非常 的最低限度,这是您在 运行 中 ghci 中 imports/definitions 未显示的代码我们

尽可能少地更改它并将其设置为 运行 给了我这个:

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.HashMap.Strict as HashMap
import qualified Data.Text.IO as StrictIO
import qualified Data.Text as Text

myFile = "data.txt"

main = do
  putStr "Reading data from file ..."
  lines <- Text.lines <$> StrictIO.readFile myFile
  putStrLn $ lines `seq` " done."

  putStr "Processing data ..."
  let hmap = HashMap.fromList $ Text.breakOn " " <$> lines
  putStrLn $ hmap `seq` " done."

我生成了一个包含 5,000,000 行的非常简单的数据文件和 运行 带有 runhaskell foo.hs 的程序,实际上 [=] 的 appea运行ce 之间有明显的停顿53=] 消息和每行出现的“完成”。

我看不出为什么所有的 IO 都会被延迟一次出现(包括第一个 putStrLn 的结果)。你实际上是如何 运行 宁这个代码(或者更确切地说,完整 and/or 不同的代码实际上 运行s)?在 post 中,您将其编写为 GHCi 的输入而不是完整的程序(根据导入和 IO 判断同一级别的语句,没有 do 块或任何顶级函数的定义)。我唯一的想法是,也许您的数据文件要小得多,以至于处理时间几乎无法察觉,并且ghcirunhaskell 对 Haskell 代码本身的初始启动处理是唯一明显的延迟;然后我可以想象在打印所有消息之后会有轻微的延迟一次。