4

我今天写了这个:

data Door = A | B | C
 deriving (Eq,Bounded,Enum)

instance Random Door where
 randomR (lo,hi) g = (toEnum i, g')
  where (i,g') = randomR (fromEnum lo, fromEnum hi) g
 random = randomR (minBound,maxBound)

而且我认为这对于任何枚举来说都是大致可复制粘贴的。我尝试将 Random 放入派生子句中,但失败了。

然后我在网上搜索并发现了这个:

请为 Random #21 提供 (Enum a, Bounded a) 的实例

几句话似乎回答了我的问题,但我不太明白:

您想到什么实例,实例 (Bounded a, Enum a) => Random a where ...?不可能有这样的实例,因为它会与其他所有实例重叠。

这将阻止任何用户派生的实例。...

为什么不能通过派生子句或至少使用默认实现来自动化。

为什么这行不通?

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)
4

1 回答 1

7

评论指的是在 Haskell 中(实际上是在带有FlexibleInstances扩展的 Haskell 中),实例匹配是通过匹配类型而不考虑约束来完成的。 类型匹配成功后,会检查约束,如果不满足会产生错误。所以,如果你定义:

instance (Bounded a, Enum a) => Random a where ...

您实际上是在为每种类型定义一个实例a,而不仅仅是a具有BoundedEnum实例的类型。就好像你写过:

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 时会收到一个错误。randomRGHC 已确定您定义的实例现在与Int. (这实际上是一个巧合,Int两者兼而有之——它也会BoundedEnum的内置实例重叠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自动派生为Enumand的任何类型,但社区不愿意添加额外的自动派生机制,特别是对于那些不经常使用Bounded的类型类(与or相比) . 所以,再一次,它永远不会发生。RandomShowEq

相反,允许方便的默认实例的标准方法是定义一些可以在显式实例定义中使用的辅助函数,这就是您链接的提案底部所建议的。例如,可以在 中定义以下函数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

在您自己的代码中。

于 2018-08-12T20:25:55.713 回答