即使使用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  

但是,如果我的问题中的代码drawMapmain.

回答

严格是一条红鲱鱼。库没有正确执行并发。一些源潜水显示了这一点:

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程序被强行销毁之前至少有一秒钟的时间来调用它——对于计算机来说,这几乎是永恒的。


以上是即使使用bang模式也无法调用IO()函数的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>