Haskell类型声明中符号`!`的含义是什么?
Haskell数据类型声明中
!符号的作用 !是Haskell中的严格性标注,用于数据类型的字段声明时,强制要求该字段在构造数据值的阶段就被求值到弱首范式(WHNF),覆盖Haskell默认的惰性求值规则。
添加!和不添加的核心差异
我们用下面的示例代码做对比说明:
-- 无严格标注的惰性字段类型 data LazyFoo = LazyFoo { lazyField :: Int } deriving (Show) -- 加!的严格字段类型 data StrictFoo = StrictFoo { strictField :: !Int } deriving (Show)
两者的差异主要体现在三个方面:
- 构造时的求值行为不同
不加!的字段默认是惰性的,构造数据时字段会存储未求值的Thunk(待执行的表达式块),直到程序第一次主动访问该字段时才会实际计算值。例如执行let val = LazyFoo (1 + 2)时,lazyField里存的是1+2的表达式,直到读取lazyField的时候才会计算得到3。
加!的字段会在构造数据时立刻求值。执行let val = StrictFoo (1 + 2)时,会直接把1+2计算为3存入字段,不需要等到访问的时候才计算。 - 异常表现不同
不加!的字段如果传入底层值为⊥(比如undefined、死循环等错误值),只要程序永远不访问这个字段,整个数据值就可以正常使用,不会抛出错误:let val = LazyFoo undefined in "运行正常"这条语句执行不会报错。
加!的字段如果传入⊥,构造数据的瞬间就会触发求值直接抛出错误:let val = StrictFoo undefined in "运行正常"这条语句执行会直接报错。 - 内存性能表现不同
不加!的字段如果长期不被访问,Thunk会持续占用额外内存,大量Thunk堆积就会引发Haskell中常见的空间泄漏问题。
加!的字段可以避免不必要的Thunk堆积,对于确定一定会被访问的小型字段,能有效降低内存开销,也能规避很多惰性求值带来的意外性能损耗。
补充说明
如果开启了GHC的StrictData扩展,模块内所有自定义数据类型的字段默认都会是严格的,不需要逐个加!;如果需要某个字段保留惰性,可以用~符号做惰性标注。
内容的提问来源于stack exchange,提问作者user16830321
相关产品推荐
相关产品推荐

