尽管 Haskell 很懒,如何输出进度信息? [复制]
Posted
技术标签:
【中文标题】尽管 Haskell 很懒,如何输出进度信息? [复制]【英文标题】:How to output progress information in spite of Haskell's laziness? [duplicate] 【发布时间】:2022-01-17 09:24:54 【问题描述】:今天我想让 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
仅在其第一个参数被评估为弱头范式时才返回。它确实有效。我的程序的输出现在暂时什么都没有,然后,一旦工作完成,所有的 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."
【问题讨论】:
您需要使用deepseq
来实现此目的。 deepseq
强制对表达式进行全面评估,直到没有剩余的 thunk。
对于您在 println 和 HashMap 中使用 <$!> 和 seq
的行示例,您可以尝试 it's strict version
@pedrofurla 我正在使用严格的哈希图。我不认为弱头范式是我的问题,因为在第二个版本中,确实输出“完成”不会过早出现。只有在第二个版本中,ghc 决定将 all 输出延迟到最后一刻。
this question 关于输出延迟的可能重复。
@DanielWagner 确实,我想念hFlush stdOut
【参考方案1】:
你没有给我们你所说的具有这种行为的实际代码:
我的程序的输出现在暂时什么都没有,然后,一旦工作完成,所有的 IO 都会发生。
我怎么知道这不是您正在运行的代码?您的代码根本无法编译以运行!几个问题:
-
您从
lines
收到类型错误,因为它在标准 Prelude
中,但该版本适用于 String
,而您正在使用 Text
。
您尚未从任何地方导入splitOn
很明显要导入的splitOn
来自Data.Text
,但它具有Text -> Text -> [Text]
类型,即它返回一个列表 的Text
在所有出现的分隔符处拆分。您显然期待一对,仅在第一个分隔符上拆分。
因此,至少非常,这是您在未向我们展示的更多导入/定义之后在ghci
中运行的代码。
尽可能少地改变它并让它运行给了我这个:
-# 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
运行程序,实际上在读取/处理消息的出现和每行出现的“完成”之间存在明显的停顿。
我看不出为什么所有 IO 会立即出现延迟(包括第一个 putStrLn
的结果。您实际上是如何运行此代码的(或者更确切地说,是实际运行的完整和/或不同的代码) )? 在帖子中,您将其编写为 GHCi 的输入,而不是完整的程序(根据导入和同一级别的 IO
语句判断,没有 do
块或任何***函数的定义)。我唯一的想法是,也许您的数据文件要小得多,以至于处理花费的时间几乎无法察觉,而 ghci
或 runhaskell
对 Haskell 代码本身的初始启动处理是唯一明显的延迟;然后我可以想象会出现一点延迟,然后似乎同时打印所有消息。
【讨论】:
首先,我知道我的方法因此原则上可行。其次,我必须提供一个实际的最小示例,始终显示我所看到的行为。 根据您的回复编辑了我的问题以上是关于尽管 Haskell 很懒,如何输出进度信息? [复制]的主要内容,如果未能解决你的问题,请参考以下文章