理解Haskell seq

理解Haskell seq,haskell,Haskell,我是Haskell的新手,在理解seq时遇到问题,我在下面的示例中对此进行了总结 这里有两个相同(愚蠢)函数的实现。该函数取正整数n并返回元组(n,n)。答案是通过使用带有元组累加器的helper函数从(0,0)开始计算得出的。为了避免建立一个大的thunk,我使用seq使累加器严格 在testSeq1中,元组的内容是严格的,而在testSeq2中,元组本身是严格的。我认为这两个实现的性能相同。但实际上,testSeq1的“总内存使用量”只有1MB,而testSeq2的“总内存使用量”却高达18

我是Haskell的新手,在理解
seq
时遇到问题,我在下面的示例中对此进行了总结

这里有两个相同(愚蠢)函数的实现。该函数取正整数n并返回元组
(n,n)
。答案是通过使用带有元组累加器的helper函数从
(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)

seq
ing一个元组只会强制对其求值,并公开其元组构造函数,但不会对其组件求值

就是

let pair = id (1+1, 2+2)
in seq pair $ ...
将应用
id
并生成
(\u-thunk1,\u-thunk2)
,其中
\u-thunk
s指向此时未评估的添加项

在第二个示例中,您正在强制累加器
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
是一种更简单的方法

作为另一种选择,使用。这样的元组从来没有组件的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))

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
将不会,因为元组构造函数被计算并返回一个未计算内容的元组。如果对它们进行求值,它们将是
未定义的

这是对元组构造函数的特殊处理,还是说对任何构造函数的参数都不会进行求值?假设我创建了自己的元组数据类型,而不是使用内置的元组,结果会是一样的吗?这对元组来说并不特殊;发生这种情况是因为Haskell很懒惰,只根据需要进行计算。你可以用
未定义的
或者你自己的数据类型做一个类似的例子来测试这一点。我认为seq的全部要点是能够覆盖Haskell的默认“惰性”,并强制它对表达式进行完全求值。这就解释了为什么我尝试使用foldl“并没有像我预期的那样有效——我在累加器函数中使用了元组构造函数。@BillyBadBoy这就是seq的全部要点,但lazy/strict不是一个全有或全无的命题;数据结构可以有很多字段,并且嵌套得很深,如果在实现函数时调用一个完全惰性的函数,那么急切地评估部分而不是全部这样的结构可能是有意义的。seq基本上是增加严格性的“最小单位”。还有一个deepseq,它可以对事物进行全面评估,但这是有代价的(遍历整个结构以检查thunks)。