尽管 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 -&gt; Text -&gt; [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 块或任何***函数的定义)。我唯一的想法是,也许您的数据文件要小得多,以至于处理花费的时间几乎无法察觉,而 ghcirunhaskell 对 Haskell 代码本身的初始启动处理是唯一明显的延迟;然后我可以想象会出现一点延迟,然后似乎同时打印所有消息。

【讨论】:

首先,我知道我的方法因此原则上可行。其次,我必须提供一个实际的最小示例,始终显示我所看到的行为。 根据您的回复编辑了我的问题

以上是关于尽管 Haskell 很懒,如何输出进度信息? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

Haskell 分析

如何让 cURL 不显示进度条但仍提供统计信息?

尽管总内存使用量只有 22Mb,但 Haskell 线程堆溢出?

在haskell中记忆多维递归解决方案

Kyuubi 输出任务进度信息改造

Ruby Lazy Enumerable flat_map 不是很懒