为什么 Either 派生 Show 而 Maybe 不派生?

Why does Either derives Show but Maybe does not?

Either and Maybe 的文档表明它们具有 Show.

的实例

Either 被定义为派生 Show,简单地说:

data  Either a b  =  Left a | Right b
  deriving (Eq, Ord, Read, Show, Typeable)

然而,Maybe 没有:

data  Maybe a  =  Nothing | Just a
  deriving (Eq, Ord)

既然它们是 base 的一部分并且非常相似,为什么 Maybe 不直接派生 Show

另一个问题可能是,它从哪里获得它的 Show 实例?

Maybe 的实例在 GHC.Show 中明确定义,还有一大堆其他常见类型(如元组)的实例。您可以使用 ghci:

中的 :i 命令找出实例的定义位置
Prelude> :i Maybe
data Maybe a = Nothing | Just a     -- Defined in ‘Data.Maybe’
instance Eq a => Eq (Maybe a) -- Defined in ‘Data.Maybe’
instance Monad Maybe -- Defined in ‘Data.Maybe’
instance Functor Maybe -- Defined in ‘Data.Maybe’
instance Ord a => Ord (Maybe a) -- Defined in ‘Data.Maybe’
instance Read a => Read (Maybe a) -- Defined in ‘GHC.Read’
instance Show a => Show (Maybe a) -- Defined in ‘GHC.Show’

我不知道他们为什么明确定义实例或将其放在 GHC.Show 而不是 Data.Maybe 中——据我所知,它可以移至 Data.Maybe and/or 导出。我的猜测是他们不希望 Data.Maybe 依赖除 GHC.Base 之外的任何东西(就像现在一样),大概是因为它被用在其他一些核心模块中。

AFAIK 元组未在任何地方定义,因此为避免孤立实例[1],必须在 GHC.Show[2] 中定义元组的 Show 实例。这些实例的实现恰好使用 foldr1:

show_tuple :: [ShowS] -> ShowS
show_tuple ss = showChar '('
              . foldr1 (\s r -> s . showChar ',' . r) ss
              . showChar ')'

so 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] 孤立实例是在与实例中涉及的 class 和类型不同的模块中定义的实例。形式上,Haskell 要求在被编译的模块直接或间接导入的任何模块中进行实例搜索;但对于非孤儿实例,GHC 可以将其短路并只在两个地方查找。对于孤儿实例,它必须跟踪模块中的每个孤儿实例,然后跟踪这些实例是否被导入它们的每个模块重新公开,这是更昂贵的(并且意味着它必须保持一个上下文环境,其中可能有许多实例甚至与当前模块无关,因为它实际上并不导入那些 classes 或类型)。因此,好的做法是避免出现孤儿实例。

从哲学上讲,孤立实例是在您的程序中获得相同 class / 类型的两个冲突实例的一种非常好的方法,因为它们在您的 [=15] 中都是 'visible' =] 模块意味着它们会发生冲突。所以语言功能本身有点狡猾。

[2] IIRC GHC 仅提供 Show 个实例,最多(相对较小)固定数量的元组组件,这不是 相当 Haskell 98 兼容,但足以满足任何实际编程需要。 (说真的,无论如何不要使用超过 3 个元素的元组,你 忘记特定组件的含义)。我不知道在过去几年中是否更新了标准以使 GHC 合规。