6

两者的文档EitherMaybe表明它们具有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实例?

4

2 回答 2

10

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

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和/或派生。我的猜测是他们不想Data.Maybe依赖任何东西,除了GHC.Base(就像现在一样),大概是因为它被用于其他一些核心模块。

于 2015-01-07T22:22:06.320 回答
4

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,它位于Maybemonad 中(我猜是老的 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 符合要求。

于 2015-01-07T22:54:26.227 回答