为什么putStrLn不是原子性操作?

3
为了练习并发编程,我写了下面这个(不太优化的)程序,它会反复计算输入值之后第一个素数的值:
import Control.Concurrent
import Control.Concurrent.Chan
import Control.Monad (forever)
primeAtLeast n = -- Some pure code that looks up the first prime at least as big as n

outputPrimeAtLeast n = putStrLn $ show $ (n, primeAtLeast n)

main = do
    chan <- newChan
    worker <- forkIO $ forever $ readChan chan >>= outputPrimeAtLeast
    forever $ (readLn :: (IO Int)) >>= (writeChan chan)
    killThread worker

我希望在后台有一个工作线程来执行实际的计算,并在完成后立即输出 (n, 最小素数n)。目前的情况是:一旦我输入数字 n,它会立即输出 (n, 并将控制权返回给主线程,在后台计算 primeAtLeast n,并在完成后输出第二部分 最小素数n)。所以,putStrLn 不是原子性的吗?还是问题出在哪里?

说实话,我有点喜欢 showputStrLn 的这种行为 ;) - Random Dev
1
顺便问一下:你想知道如何修复它(请参见@chi s的答案),还是想知道为什么(懒惰IO /评估-字符串是char列表)? - Random Dev
@CarstenKönig,所以输出逐个字符进行?同意,这非常棒。 - Turion
3
putStrLn 可以处理无限长的字符串,或者在你输出部分初始内容后引发异常的字符串,例如 show (1, undefined)。如果它是原子的,就不能处理这种情况。 - Ørjan Johansen
1个回答

3

试试这个:

outputPrimeAtLeast n = let p = primeAtLeast n in p `seq` putStrLn $ show (n, p)

上述代码会在运行 putStrLn 前计算出质数。此外,你也可以使用 print 代替 putStrLn . show
outputPrimeAtLeast n = let p = primeAtLeast n in p `seq` print (n, p)

另外,您可以使用putStrLn函数,在开始打印之前强制处理每个字符。

strictPutStrLn :: Show a => a -> IO ()
strictPutStrLn x = let str = show x in str `listSeq` putStrLn str

listSeq :: [a] -> b -> b
listSeq []     w = w
listSeq (x:xs) w = x `seq` listSeq xs w

出于好奇,为什么使用 where 而不是 let 不起作用?如果我这样做,会出现“超出范围”的错误。 - Turion
@Turion where 应该也可以……你是否将其缩进至少一个空格比 outputPrimeAtLeast 更靠右? - chi
似乎这与在Monad中使用它有关: main = do n <- readLn :: IO Int print x where x = n*2 这会产生相同的错误。 - Turion
2
@Turion,你不能使用where来引用在do(或letcase等)中绑定的变量。where只能看到函数参数。 - chi
1
@chi 实际上对于 case 语句来说并不是这样的,但你必须在想要使用变量的匹配项上加上 where - Ørjan Johansen
@ØrjanJohansen 哦,有趣!我以前从未见过这种写法。显然 case ... of { pattern -> expression where bindings } 是有效的语法。知道了真好,谢谢。 - chi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接