为什么可以';我们在Haskell中是否有为枚举派生的随机类实例?

为什么可以';我们在Haskell中是否有为枚举派生的随机类实例?,haskell,enums,typeclass,deriving,Haskell,Enums,Typeclass,Deriving,我今天写道: 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) 我认为这对于任何枚举来说都是可以复制的。 我试图在派生子句中加入Rand

我今天写道:

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,但失败了

然后我在网上搜索发现:

以下几句话似乎回答了我的问题,只是我不太明白:

您心目中的实例是什么,实例(有界a,枚举a)=> 随机a在哪里。。。?不可能有这样的例子,因为它会 与其他实例重叠

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

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

为什么这行不通

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)

注释指的是,在Haskell中(实际上在Haskell中使用
FlexibleInstances
扩展名),实例匹配是通过匹配类型而不考虑约束来完成的类型匹配成功后,将检查约束,如果不满足约束,将生成错误。因此,如果您定义:

instance (Bounded a, Enum a) => Random a where ...
实际上,您正在为每个类型
a
定义一个实例,而不仅仅是具有
有界的
枚举
实例的类型
a
。就好像你写过:

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
扩展来允许对实例进行约束。这很好,但是如果您添加了它,您将看到您需要
不可判定实例
扩展。这可能不太好,但如果你加上它,你会发现在
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 (Bounded [Char]) arising from a use of ‘randomIO’
因为您的实例匹配
字符串
,但GHC找不到
有界字符串
约束

总之,一般来说,Haskell社区避免将这些类型的catch-all实例放入标准库。他们需要
不可分类的实例
扩展和
可重叠的
杂注,并可能在程序中引入大量不受欢迎的实例,这一事实在人们的口中留下了不好的印象

因此,虽然在技术上可以将这样一个实例添加到
System.Random
,但它永远不会发生

同样,对于
Enum
Bounded
的任何类型,都可以允许自动派生
Random
,但是社区不愿意添加额外的自动派生机制,特别是对于像
Random
这样不经常使用的类型类(与Show
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

在您自己的代码中。

请注意
实例(有界a,枚举a)=>随机a
不是有效的实例声明。请参阅:。这将与任何其他实例重叠,因为Haskell在考虑重叠时不检查上下文(需要exptime放大的IIRC)。此外,如果我想为我的自定义枚举类型使用不同的发行版,该怎么办?在多个重叠实例之间需要一种优先级。GHC有一些,但它可能非常脆弱。重叠(和不连贯)实例可能太不可预测——我会尽可能避免它们。@chi“Haskell在考虑重叠时不检查上下文”这不是针对正常函数进行的检查吗?算法不是“通过增加通用性进行排序,并选择匹配的第一个吗工作?这似乎是:nlogn sort+n检查。如果在上下文失败时,需要检查列表中的下一个,则需要回溯,大致与运行Prolog程序时相同。每个“n检查”都可以触发其他“m检查”,它可以触发…等等。Haskell检查所有实例的实例头,但只检查一个实例的实例上下文,这样我们递归地得到n+m+…而不是nm…实际的算法很复杂,我想你可以在GHC手册中找到它,查找重叠的实例。如果constr不匹配?在重叠的情况下,更专业的(具体类型,而不是类型变量)获胜。这不是很明显吗?关于第1点,可能对您来说很明显,但对GHC的类型检查器来说不是。问题在于,在内部,类型检查器不能表示“未完全指定的类型”
instance {-# OVERLAPPABLE #-} (Bounded a, Enum a) => Random a where
    randomR = defaultEnumRandomR
    random = defaultBoundedRandom