即使使用bang模式也无法调用IO()函数
我有一些库,它具有使用 gnuplot 库进行绘图的功能:
import Graphics.Gnuplot.Simple
drawMap :: [(Int, Int)] -> IO ()
drawMap m = do
!a <- plotList [] m
print a
我像这样从 main 调用这个函数:
main = do
!a <- drawMap [(1,2),(3,5)]
print a
我用堆栈构建项目,并尝试了 -O2 和 -O0 优化,但 plot 永远print a不起作用(函数总是被成功调用并打印())。我怎样才能强制绘图以及为什么它不工作,使用库,但如果我只是plotList从 main调用就可以工作?
更新。
在 main 和drawMapby$!中使用严格的应用程序也不起作用:
drawMap :: [(Int, Int)] -> IO ()
drawMap m = plotList [] $! m
main = do
drawMap $! [(1,2),(3,5)]
UPD 2
一些最小的例子:
这对我不起作用:
import Graphics.Gnuplot.Simple
main = plotList [] ([(1,2),(3,5)] :: [(Int,Int)])
但这有效:
{-# LANGUAGE BangPatterns #-}
import Graphics.Gnuplot.Simple
main = do
!a <- plotList [] ([(1,2),(3,5)] :: [(Int,Int)])
print a
但是,如果我的问题中的代码drawMap在main.
回答
严格是一条红鲱鱼。库没有正确执行并发。一些源潜水显示了这一点:
runGnuplot ::
Graph.C graph =>
[Attribute] -> String -> Plot.T graph -> IO ()
runGnuplot attrs cmd (Plot.Cons mp) =
void $ Cmd.asyncIfInteractive (interactiveTerm attrs) $ Cmd.run $ dir ->
let files = MR.runReader (MS.evalStateT mp 0) dir
in (map attrToProg attrs ++
[cmd ++ " " ++
extractRanges attrs ++ " " ++
commaConcat (plotFileStatements files)],
files)
interactiveTerm :: [Attribute] -> Bool
interactiveTerm =
all $ attr ->
case attr of
Terminal term -> Terminal.interactive term
PNG _ -> False
EPS _ -> False
_ -> True
asyncIfInteractive :: Bool -> IO ExitCode -> IO ExitCode
asyncIfInteractive interactive act =
if interactive
then fmap (const ExitSuccess) $ forkIO $ void act
else act
特别是,当属性列表为空时,它会被认为是“交互式的”,并且IO动作会被分叉到它自己的线程中。Haskell 程序的语义是它们在main线程退出时退出,所以这是一个竞争条件:将main首先退出,还是分叉的线程gnuplot首先调用?
这里正确的做法是为runGnuplot用户提供一个IO等待分叉线程完成的操作,他们可以从他们的main线程中调用该操作(例如,通过分配一个MVar,在分叉线程中写入它,并在返回的动作)。简单的错误做法是将 athreadDelay扔到您的程序中:
import Graphics.Gnuplot.Simple
import Control.Concurrent
main = do
plotList [] ([(1,2),(3,5)] :: [(Int,Int)])
threadDelay 1000000
这仍然是一个竞争条件,但现在分叉的线程在gnuplot程序被强行销毁之前至少有一秒钟的时间来调用它——对于计算机来说,这几乎是永恒的。