2.4 Deepseq
Haskell并行与并发编程
前面已经用过了force函数,其具体类型如下:
force :: NFData a => a -> a
函数force会对其参数完全求值,然后返回。不过该函数并非内建,而是针对每种数据类型,通过NFData类对该函数的行为进行定义。NFData的意思是范式数据(normal-form data),其中范式是指不包含未被求值子表达式的值,“数据”则表示范式中不包含函数,因为无法看到函数里面的内容并对里面的内容求值1。
类NFData仅包含一个方法:
seq
class NFData a where
rnf :: a -> ()
rnf a = a()
函数rnf名字的意思是“约化为范式”(reduce to normal-form)。该函数对其参数完全求值,然后返回()。默认通过seq来实现,对于没有子结构的类型来说很方便,只需使用默认定义即可。例如:Bool的实例可以简单地定义:
instance NFData Bool
模块Control.Deepseq对库中所有其他的常见类型均提供了实例。
对于自定义的类型,可能需要实现相应的NFData的实例。例如,对于二叉树类型:
data Tree a = Empty | Branch (Tree a) a (Tree a)
那么,其NFData的实例如下:
instance NFData a => NFData (Tree a) where
rnf Empty = ()
rnf (Branch l a r) = rnf l `seq` rnf a `seq` rnf r
实现的思路为递归地对数据类型的组成部分应用rnf,然后将这些rnf的调用通过seq组合起来。
Control.DeepSeq模块中还有一些其他运算,例如:
deepseq :: NFData a => a -> b -> b
deepseq a b = rnf a `seq` b
函数deepseq因其和seq的相似性而得名:和seq类似,都是强制求值。如果把弱首范式看成是浅(shallow)求值,那么范式就是深(deep)求值,因此得名deepseq。
函数force是通过deepseq定义的:
force :: NFData a => a -> a
force x = x `deepseq` x
函数force的功能应该被认为是将WHNF转换为NF(normal form),也就是,当程序将force x求值成WHNF时,x会被求值成NF。
图像说明文字将表达式求值为范式需要对整个数据结构进行遍历,因此需要铭记,对于大小为n的数据结构,其复杂度是O(n),而对seq来说,只有O(1)。因此,应该避免对同一数据反复使用force或deepseq函数。
WHNF和NF就像天平的两端,但是,根据数据类型的不同,还有许多处于中间的“不同程度的求值”。例如:前面的length函数对列表的脊(spine)求值,即展开了列表,但未对列表的元素求值。parallel包中的模块Control.Seq提供了一系列组合子,通过组合,可以对数据结构达到不同程度的求值。本书虽然不会在例子中使用这些运算,但对读者也许会有所帮助。
1不过,纯粹为了方便,定义了一个函数的NFData实例,会将函数求值为WHNF(弱首范式),因为经常会有数据结构里面包含函数,而尽管如此,还是想尽可能地对这样的数据结构进行求值。