为什么 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 合规。
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 合规。