[英]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
返回值之前將評估a
和b
兩者。 特別是,這意味着可以在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
返回其值時,將評估x
和freeFile
。 但是,再次,首先評估x
或freeFile
一個? 由於我沒有得到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.