递归类型中经eta缩减的genericShow版show定义为何栈溢出?
为什么eta缩减的
show = genericShow会引发栈溢出,而eta展开写法却正常? 先看问题中的代码:
定义递归数据类型:
data Chain a = End | Link a (Chain a)
推导Generic实例后,两种Show实例定义:
- 会栈溢出的eta缩减写法:
derive instance Generic (Chain a) _ instance Show a => Show (Chain a) where show = genericShow
- 正常工作的eta展开写法:
derive instance Generic (Chain a) _ instance Show a => Show (Chain a) where show c = genericShow c
执行logShow $ Link 1 $ Link 2 $ End时,前者直接栈溢出,后者能正确输出结果。
原因分析
问题的核心在于值级循环引用和函数求值的时机差异:
genericShow是基于Generic实例生成的通用序列化函数,对于递归类型Chain a,它处理Link构造器时,会递归调用show来序列化子Chain a值。- 当用eta缩减
show = genericShow时,show被直接绑定为genericShow本身。这就形成了一个死循环:genericShow需要调用show,而show就是genericShow,没有机会根据实际的Chain值(比如End)触发终止逻辑。求值时,这个无限递归会直接耗尽栈空间。 - 而eta展开的
show c = genericShow c是一个匿名函数,每次调用show时才会传入具体的c并执行genericShow。此时genericShow会根据c的构造器分支处理:遇到End就终止递归,遇到Link就分别处理元素和子链,完全符合递归终止的要求,因此不会栈溢出。
说白了,eta缩减写法让show和genericShow形成了无终止条件的循环绑定,而eta展开写法确保每次调用都基于具体的值走正确的递归分支。
内容的提问来源于stack exchange,提问作者jfMR
相关产品推荐
相关产品推荐

