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