簡體   English   中英

如何在IO monad中正確強制評估純值?

[英]How to properly force evaluation of pure value in IO monad?

我需要強制評估IO monad中的純值。 我正在為C綁定編寫更高級別的接口。 在較低級別我有,比如newFile函數和freeFile函數。 newFile返回我在較低級別定義的一些id,不透明對象。 你基本上不能做任何事情,但是用它來釋放文件純粹計算與該文件相關的東西。

所以,我有(簡化):

execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
  fid <- newFile path -- ‘fid’ stands for “file id”
  let x = runGetter g fid
  freeFile fid
  return x

這是該函數的初始版本。 我們需要在freeFile之前計算x (代碼有效,如果我刪除freeFile就可以了,但是我想釋放資源,你知道。)

第一次嘗試(我們將使用seq來“強制”評估):

execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
  fid <- newFile path
  let x = runGetter g fid
  x `seq` freeFile fid
  return x

分段故障。 直接進入seq文檔:

如果a是底部,則seq ab的值是底部,否則等於b 通常引入seq以通過避免不必要的懶惰來提高性能。

在評估順序的說明:表達seq ab不能保證a將之前評估b seq給出的唯一保證是在seq返回值之前將評估ab兩者。 特別是,這意味着可以在a之前評估b 如果需要保證特定的評估順序,則必須使用“並行”軟件包中的函數pseq

確實如此,我看到人們在這種情況下聲稱評價順序不同。 pseq怎么樣? 我是否需要依賴parallel只是因為pseq ,嗯...可能還有另一種方式。

{-# LANGUAGE BangPatterns #-}

execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
  fid <- newFile path
  let !x = runGetter g fid
  freeFile fid
  return x

分段故障。 那么, 這個答案對我來說不起作用。 但它建議evaluate ,讓我們嘗試一下:

Control.Exception (evaluate)
Control.Monad (void)

execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
  fid <- newFile path
  let x = runGetter g fid
  void $ evaluate x
  freeFile fid
  return x

分段故障。 也許我們應該使用評估返回的evaluate

Control.Exception (evaluate)
Control.Monad (void)

execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
  fid <- newFile path
  let x = runGetter g fid
  x' <- evaluate x
  freeFile fid
  return x'

不,不好主意。 也許我們可以鏈接seq

execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
  fid <- newFile path
  let x = runGetter g fid
  x `seq` freeFile fid `seq` return x

這有效。 但這是正確的方法嗎? 也許它只能由於一些易變的優化邏輯而起作用? 我不知道。 如果seq在這種情況下左側關聯,那么根據該描述,當return x返回其值時,將評估xfreeFile 但是,再次,首先評估xfreeFile一個? 由於我沒有得到seg故障,它必須是x ,但這個結果是否可靠? 你知道怎么給力的評價x之前freeFile 正常嗎?

一個可能的問題是newFile正在做一些懶惰的IO,而runGetter是一個足夠懶惰的消費者,在其輸出上運行seq並不會強制所有newFile的IO實際發生。 這可以通過使用deepseq而不是seq來修復:

execGetter :: NFData a => FilePath -> TagGetter a -> IO a
execGetter path g = do
  fid <- newFile path
  let x = runGetter g fid
  x `deepseq` freeFile fid
  return x

這將解決的另一種可能性是runGetter聲稱是純粹的,但實際上並不是(並且是一個懶惰的生產者)。 但是,如果是這種情況,正確的修復不是在這里使用deepseq ,而是為了從runGetter消除unsafePerformIO的使用,然后使用:

execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
  fid <- newFile path
  x <- runGetter g fid
  freeFile fid
  return x

這應該工作,而不用進一步擺弄強迫。

Daniel Wagner的答案對於這個用例很好,但有時候只評估列表的NFData並使列表元素不被評估(有時列表項沒有合理的NFData實例)也是有益的。 您不能使用deepseq ,因為它會評估所有內容。 但是,您可以將seq與此功能結合使用:

evalList :: [a] -> ()
evalList [] = ()
evalList (_:r) = evalList r

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM