即使使用 bang 模式也无法调用 IO () 函数

Cannot call IO () function even with bang patterns

我有一些库具有使用 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 优化,但绘图永远不起作用(print a 函数总是被成功调用并打印 ())。 我如何强制绘图以及为什么它对库不起作用,但如果我只是从 main 调用 plotList

UPD.

在 main 和 drawMap by $! 中使用严格的应用程序也不起作用:

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  

但是如果 drawMapmain.

之外的其他模块中,即使使用 bang patterns/strict 应用程序,我的问题中的代码也不起作用

严格是转移注意力的问题。该库没有正确执行并发。一些来源潜水显示:

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,在分叉线程中写入它,并在返回的操作中从中读取)。一个简单的错误做法是将 threadDelay 扔进你的程序中:

import Graphics.Gnuplot.Simple
import Control.Concurrent

main = do
    plotList [] ([(1,2),(3,5)] :: [(Int,Int)])
    threadDelay 1000000

这仍然是一个竞争条件,但现在分叉线程至少有一秒钟的时间在程序被强制销毁之前完成其 gnuplot 调用 -- for a computer, that is nearly an eternity.