评论指的是在 Haskell 中(实际上是在带有FlexibleInstances
扩展的 Haskell 中),实例匹配是通过匹配类型而不考虑约束来完成的。 类型匹配成功后,会检查约束,如果不满足会产生错误。所以,如果你定义:
instance (Bounded a, Enum a) => Random a where ...
您实际上是在为每种类型定义一个实例a
,而不仅仅是a
具有Bounded
和Enum
实例的类型。就好像你写过:
instance Random a where ...
这将可能与任何其他库定义或用户定义的实例发生冲突,例如:
newtype Gaussian = Gaussian Double
instance Random Gaussian where ...
有办法解决这个问题,但整个事情最终变得非常混乱。此外,它可能会导致一些神秘的编译类型错误消息,如下所述。
具体来说,如果您将以下内容放入模块中:
module RandomEnum where
import System.Random
instance (Bounded a, Enum a) => Random a where
randomR (lo,hi) g = (toEnum i, g')
where (i,g') = randomR (fromEnum lo, fromEnum hi) g
random = randomR (minBound,maxBound)
你会发现你需要FlexibleInstances
扩展来允许对实例的约束。这很好,但如果你添加它,你会看到你需要UndecidableInstances
扩展。这可能不太好,但如果你添加它,你会发现你在调用你的定义randomR
的 RHS 时会收到一个错误。randomR
GHC 已确定您定义的实例现在与Int
. (这实际上是一个巧合,Int
两者兼而有之——它也会Bounded
与Enum
的内置实例重叠Double
,但两者都不是。)
无论如何,您可以通过使您的实例可重叠来解决此问题,以便执行以下操作:
{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}
module RandomEnum where
import System.Random
instance {-# OVERLAPPABLE #-} (Bounded a, Enum a) => Random a where
randomR (lo,hi) g = (toEnum i, g')
where (i,g') = randomR (fromEnum lo, fromEnum hi) g
random = randomR (minBound,maxBound)
实际上会编译。
这大部分都很好,但您最终可能会收到一些奇怪的错误消息。通常,以下程序:
main = putStrLn =<< randomIO
会产生合理的错误信息:
No instance for (Random String) arising from a use of `randomIO'
但是有了上面的例子,它就变成了:
No instance for (Bounded [Char]) arising from a use of ‘randomIO’
因为您的实例匹配String
但 GHC 未能找到Bounded String
约束。
无论如何,总的来说,Haskell 社区避免将这些包罗万象的实例放入标准库中。他们需要UndeciableInstances
扩展和OVERLAPPABLE
编译指示并可能在程序中引入一堆不受欢迎的实例这一事实都会在人们口中留下不好的味道。
因此,虽然在技术上可以将这样的实例添加到 中System.Random
,但它永远不会发生。
同样,可以允许Random
自动派生为Enum
and的任何类型,但社区不愿意添加额外的自动派生机制,特别是对于那些不经常使用Bounded
的类型类(与or相比) . 所以,再一次,它永远不会发生。Random
Show
Eq
相反,允许方便的默认实例的标准方法是定义一些可以在显式实例定义中使用的辅助函数,这就是您链接的提案底部所建议的。例如,可以在 中定义以下函数System.Random
:
defaultEnumRandomR :: (Enum a, RandomGen g) => (a, a) -> g -> (a, g)
defaultEnumRandomR (lo,hi) g = (toEnum i, g')
where (i,g') = randomR (fromEnum lo, fromEnum hi) g
defaultBoundedRandom :: (Random a, Bounded a, RandomGen g) => g -> (a, g)
defaultBoundedRandom = randomR (minBound, maxBound)
人们会写:
instance Random Door where
randomR = defaultEnumRandomR
random = defaultBoundedRandom
这是唯一有机会进入System.Random
.
如果确实如此,并且您不喜欢必须定义显式实例,那么您可以自由坚持:
instance {-# OVERLAPPABLE #-} (Bounded a, Enum a) => Random a where
randomR = defaultEnumRandomR
random = defaultBoundedRandom
在您自己的代码中。