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
实例?
答案 0 :(得分: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
之外的任何东西(就像现在一样),大概是因为它在某些其他核心模块中使用过。
答案 1 :(得分: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
,它位于Maybe
monad中(我猜想旧的Haskell 98的单态偏差)。所以GHC.List导入Data.Maybe。为了定义Show
实例,Data.Maybe需要导入GHC.Show(直接或间接),这将使整个序列GHC.Show - > GHC.List - > Data.Maybe - > GHC.Show循环依赖。 GHC并不能很好地支持循环依赖(并不是说它们很容易支持!),所以基础很难避免它们。
[1]孤立实例是在与实例中涉及的类和类型不同的模块中定义的实例。形式上,Haskell要求实例搜索在任何模块中完成,直接或由正在编译的模块间接导入;但是对于非孤儿实例,GHC可能会短路,只能看两个地方。对于孤立实例,它必须跟踪模块中的每个孤立实例,然后跟踪每个导入它们的模块重新暴露这些实例,这更昂贵(并且意味着它必须保持具有潜在的许多实例的上下文环境)甚至与当前模块无关,因为它实际上并没有导入这些类或类型)。因此,良好的做法是避免孤立实例。
更哲学上,孤立实例是一种非常好的方法,可以在程序中获得同一类/类型的两个冲突实例,因为它们都是可见的'在Main
模块中意味着它们会发生冲突。所以语言功能本身就是一种狡猾的。
[2] IIRC GHC仅提供Show
个实例,最多包含一个(相对较小的)固定数量的元组组件,这些组件不符合Haskell 98的要求但是足够好适合任何实际的编程需求。 (说真的,不要使用超过3个元素的元组,你将忘记具体组件的含义)。我不知道该标准是否已更新,以便在过去几年内使GHC符合要求。