Haskell:打印连接的无限列表的编译函数与解释函数之间的奇怪差异
Haskell: odd difference between compiled vs interpreted functions which print concatenated infinite lists
我探索 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)
如果我 运行 它来自前奏,它开始连接一个类似于 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)
prelude 的相同类型结果:
λ> putStrLn func
1591317212529333741454953576165697377818589
...等等
按预期工作,当然比 pi 函数快得多,因为计算不那么复杂。
现在让我感到困惑的部分是:
如果我编译:ghc pi.hs
,并且 运行 我的程序:./pi
,输出将永远保持空白,直到我发送中断信号。在那一刻,整个计算的 pi 字符串立即显示出来。它不会 "stream" 输出到标准输出,就像 GHCI 那样。好吧,我知道他们并不总是以同样的方式行事。
但接下来我 运行: ghc func.hs
, 运行 我的程序: ./func
... 它立即开始打印字符列表。
这种差异从何而来?我想这可能是因为我愚蠢无用的小函数(最终)重复了,所以编译器可以 "predict" 输出更好?
或者这些函数的工作方式之间还有其他根本区别吗?还是我在做一些非常愚蠢的事情?
解决方案/答案
下面由 Thomas & Daniel 提供,我是:
- 不耐烦。大块最终会出现在 pi 函数中,它在我简单的旧编码机器上有点慢。
- 不以任何方式处理缓冲。
所以重写主函数后:
import System.IO
main :: IO ()
main = do hSetBuffering stdout NoBuffering
putStrLn pi'
已修复!
Thomas & Daniel 在评论中提供,原来我是:
- 不耐烦。大块最终会出现在 pi 函数中,它在我简单的旧编码机器上有点慢。
- 不以任何方式处理缓冲。
所以重写main函数后:
import System.IO
main :: IO ()
main = do hSetBuffering stdout NoBuffering
putStrLn pi'
已修复!
我探索 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)
如果我 运行 它来自前奏,它开始连接一个类似于 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)
prelude 的相同类型结果:
λ> putStrLn func
1591317212529333741454953576165697377818589
...等等
按预期工作,当然比 pi 函数快得多,因为计算不那么复杂。
现在让我感到困惑的部分是:
如果我编译:ghc pi.hs
,并且 运行 我的程序:./pi
,输出将永远保持空白,直到我发送中断信号。在那一刻,整个计算的 pi 字符串立即显示出来。它不会 "stream" 输出到标准输出,就像 GHCI 那样。好吧,我知道他们并不总是以同样的方式行事。
但接下来我 运行: ghc func.hs
, 运行 我的程序: ./func
... 它立即开始打印字符列表。
这种差异从何而来?我想这可能是因为我愚蠢无用的小函数(最终)重复了,所以编译器可以 "predict" 输出更好?
或者这些函数的工作方式之间还有其他根本区别吗?还是我在做一些非常愚蠢的事情?
解决方案/答案
下面由 Thomas & Daniel 提供,我是:
- 不耐烦。大块最终会出现在 pi 函数中,它在我简单的旧编码机器上有点慢。
- 不以任何方式处理缓冲。
所以重写主函数后:
import System.IO
main :: IO ()
main = do hSetBuffering stdout NoBuffering
putStrLn pi'
已修复!
Thomas & Daniel 在评论中提供,原来我是:
- 不耐烦。大块最终会出现在 pi 函数中,它在我简单的旧编码机器上有点慢。
- 不以任何方式处理缓冲。
所以重写main函数后:
import System.IO
main :: IO ()
main = do hSetBuffering stdout NoBuffering
putStrLn pi'
已修复!