Haskell:编译与解释函数之间的奇怪区别,它们打印出连接的无限列表

okd*_*wit 5 haskell list ghc ghci

我只是在探索Haskell的乐趣,并了解语言.我认为以下行为很有趣,我找不到发生这种情况的原因.

这是一个经常被引用的Haskell代码,它一直计算pi直到被中断,稍微修改以给出一个连续的字符列表而不是一个整数列表:

main :: IO ()
main =  do putStrLn pi'

pi' :: [Char]
pi' = concat . map show $ g(1,0,1,1,3,3) where
   g(q,r,t,k,n,l) =
      if 4*q+r-t<n*t
      then n : g(10*q,10*(r-n*t),t,k,div(10*(3*q+r))t-10*n,l)
      else g(q*k,(2*q+r)*l,t*l,k+1,div(q*(7*k+2)+r*l)(t*l),l+2)
Run Code Online (Sandbox Code Playgroud)

如果我从prelude运行它,它会开始连接类似pi数字的字符串:

?> putStrLn pi' 31415926535897932384626433832795028841971 ...等等

按预期工作,它立即开始喷出数字.

现在这是我刚刚写的一段具有相同结构的代码.从数学的角度来看,它完全没用,我只是在弄乱Haskell如何工作.操作要简单得多,但它确实具有相同的类型,子函数也是如此(除了较小的元组).

main :: IO ()
main =  do putStrLn func

func :: [Char]
func = concat . map show $ h(1,2,1) where
   h(a,b,c) =
     if a <= 1000
     then a : h((div a 1)+2*b,b,1)
     else h(b,div (b-3) (-1),div a a)
Run Code Online (Sandbox Code Playgroud)

前奏相同类型的结果:

?> putStrLn func 1591317212529333741454953576165697377818589 ...等等

按预期工作,虽然比pi功能快得多,因为计算不太复杂.

现在让我困惑的部分:

如果我编译:ghc pi.hs,并运行我的程序:./pi,输出永远保持空白,直到我发送一个中断信号.在那一刻,立即显示整个计算的pi字符串.它没有像GHCI那样将输出"流"到stdout.好的,我知道他们并不总是以同样的方式行事.

但接下来我运行:ghc func.hs,并运行我的程序:./func...它立即开始打印字符列表.

这种差异来自哪里?我认为这可能是因为我的愚蠢无用的小函数(最终)重复,所以编译器可以更好地"预测"输出?

或者功能的工作方式还有另一个根本区别吗?或者我在做一些完全愚蠢的事情?

解决方案/答案

由托马斯和丹尼尔提供,我是:

  1. 不耐烦.大块最终出现了pi功能,在我简单的旧编码机上只是有点慢.
  2. 不以任何方式处理缓冲.

所以重写主函数后:

import System.IO

main :: IO ()
main =  do hSetBuffering stdout NoBuffering
           putStrLn pi'
Run Code Online (Sandbox Code Playgroud)

它被修复了!

okd*_*wit 2

托马斯和丹尼尔在评论中提供,事实证明我是:

  1. 不耐烦。大块最终会通过 pi 函数出现,只是在我简单的旧编码机器上有点慢。
  2. 不以任何方式处理缓冲。

所以重写main函数后:

import System.IO

main :: IO ()
main =  do hSetBuffering stdout NoBuffering
           putStrLn pi'
Run Code Online (Sandbox Code Playgroud)

已经修好了!