2015-08-15 63 views
4

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

下面是相同(傻)功能的2个实现。该函数采用正整数n并返回元组(n,n)。答案是通过使用具有元组累加器的辅助函数从(0,0)开始计数而产生的。为了避免构建一个大型的thunk,我使用seq来使得累加器严格。

testSeq1元组的内容是严格的,而在testSeq2元组本身是严格的。我认为这两个实现将具有相同的性能。但实际上testSeq1的'使用总内存'仅为1MB,而testSeq2(当使用n = 1000000进行测试时)为187MB。

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) 

回答

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

+0

对于元组构造函数来说,这是一种特殊的处理方法,还是你说对任何构造函数的参数都不会发生评估?假设我创建了自己的元组数据类型,而不是使用内置的元组,结果会是一样的吗? – BillyBadBoy

+0

这不是特殊的元组;发生这种情况是因为Haskell是懒惰的,只会根据需要进行评估。你可以用'Just undefined'或者你自己的数据类型做类似的例子来测试。 – Tordek

+0

这对我来说很震惊。我认为seq的全部重点是能够覆盖Haskell的默认“懒惰”并迫使它完全评估一个表达式。这解释了为什么我尝试使用foldl'没有按照我的预期工作 - 我在我的累加器函数中使用了元组构造函数。 – BillyBadBoy

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))