我是Haskell新手,他在理解seq
时遇到了问题,我在下面的例子中对此进行了总结。
以下是相同(愚蠢)功能的2个实现。该函数采用正int n并返回元组(n,n)
。答案是通过使用辅助函数和元组累加器从(0,0)
计数来产生的。为了避免构建大量的thunk,我使用seq来使累加器严格。
在testSeq1
中,元组的内容是严格的,而在testSeq2
中,元组本身是严格的。我认为这两个实现将执行相同的操作。但实际上使用的总内存是' testSeq1
只有1MB,testSeq2
只有187MB(使用n = 1000000时测试)。
testSeq2
有什么问题?
testSeq1 :: Int -> (Int,Int)
testSeq1 n = impl n (0,0) where
impl 0 (a,b) = (a,b)
impl i (a,b) = seq a $ seq b $ impl (i-1) (a+1, b+1)
testSeq2 :: Int -> (Int,Int)
testSeq2 n = impl n (0,0) where
impl 0 acc = acc
impl i acc = seq acc $ impl (i-1) ((fst acc)+1, (snd acc)+1)
答案 0 :(得分:7)
seq
一个元组只会强制它被公开其元组构造函数,但不会评估它的组件。
即,
let pair = id (1+1, 2+2)
in seq pair $ ...
将应用id
并生成(_thunk1, _thunk2)
,其中_thunk
指向添加内容,此时尚未对其进行评估。
在你的第二个例子中,你强制累加器acc
,而不是它的组件,因此一些大的thunk仍在建立。
您可以使用所谓的评估策略,例如:
evalPair :: (a,b) -> ()
evalPair (a,b) = seq a $ seq b ()
testSeq2 :: Int -> (Int,Int)
testSeq2 n = impl n (0,0) where
impl 0 acc = acc
impl i acc = seq (evalPair acc) $ impl (i-1) ((fst acc)+1, (snd acc)+1)
但是,testSeq1
是一种更简单的方法。
作为另一种选择,请使用strict tuples。这样的元组永远不会有组件的thunk,但只评估结果。然后,强制元组构造函数也将强制组件,如您所料。
import Data.Strict.Tuple
testSeq2 :: Int -> Pair Int Int
testSeq2 n = impl n (0 :!: 0) where
impl 0 acc = acc
impl i acc = seq acc $ impl (i-1) ((fst acc + 1) :!: (snd acc + 1))
答案 1 :(得分:5)
seq
只会轻微地评估其第一个论点。您可以看到以下两个示例:
errorTuple :: (Int, Int)
errorTuple = undefined
errorTupleContents :: (Int, Int)
errorTupleContents = (undefined, undefined)
case1 = errorTuple `seq` (1, 1)
case2 = errorTupleContents `seq` (1, 1)
case1
会因undefined
错误而失败,因为seq
会尝试强制评估errorTuple
,undefined
,case2
不会,因为元组构造函数被计算并返回一个元组,其内容未被评估。如果他们被评估,他们将undefined
。