AFAIK 元组没有在任何地方定义,因此为了避免孤立实例[1],必须在 GHC.Show[2] 中定义元组的 Show 实例。这些实例的实现恰好使用foldr1
:
show_tuple :: [ShowS] -> ShowS
show_tuple ss = showChar '('
. foldr1 (\s r -> s . showChar ',' . r) ss
. showChar ')'
所以 GHC.Show 在定义该函数的地方导入 GHC.List 。反过来,GHC.List 定义了lookup
,它位于Maybe
monad 中(我猜是老的 Haskell 98 的单态偏差)。所以 GHC.List 导入 Data.Maybe。为了定义一个Show
实例,Data.Maybe 需要导入 GHC.Show(直接或间接),这将使整个序列 GHC.Show -> GHC.List -> Data.Maybe -> GHC.Show 循环依赖. GHC 不能很好地支持循环依赖(并不是说它们很容易支持!),所以 base 很难避免它们。
[1] 孤立实例是在与实例中涉及的类和类型不同的模块中定义的实例。形式上,Haskell 要求在被编译的模块直接或间接导入的任何模块中进行实例搜索;但对于非孤儿实例,GHC 可以将其短路,只需查看两个地方。对于孤儿实例,它必须跟踪模块中的每个孤儿实例,然后跟踪这些实例是否被导入它们的每个模块重新公开,这更昂贵(并且意味着它必须保持一个上下文环境,其中可能包含许多实例甚至与当前模块无关,因为它实际上并没有导入这些类或类型)。因此,好的做法是避免出现孤儿实例。
从哲学上讲,孤儿实例是在程序中获取同一类/类型的两个冲突实例的一种非常好的方法,因为它们在您的模块中都是“可见的”,这Main
意味着它们会发生冲突。所以语言功能本身有点狡猾。
[2] IIRC GHC 仅提供Show
最多(相对较小)固定数量的元组组件的实例,这不太符合 Haskell 98,但足以满足任何实际编程需求。(说真的,无论如何都不要使用超过 3 个元素的元组,你会忘记特定组件的含义)。我不知道该标准是否已在过去几年更新以使 GHC 符合要求。