我今天写了这个:
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 放入派生子句中,但失败了。
然后我在网上搜索并发现了这个:
Please provide instance for (Enum a, Bounded a) for Random #21
几句话似乎回答了我的问题,但我不太明白:
What instance do you have in mind, instance (Bounded a, Enum a) => Random a where ...? There can't be such an instance, since it would overlap with every other instance.
This would prevent any user derived instances. ...
为什么不能通过派生子句或至少使用默认实现来自动化。
为什么这行不通?
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 中(实际上在带有 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
上得到一个错误。调用您的randomR
的RHS定义。 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
的任何类型自动派生和 Bounded
,但社区不愿意添加额外的自动派生机制,特别是对于像 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
在您自己的代码中。
关于haskell - 为什么我们不能在 Haskell 中为枚举派生 Random 类实例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51811805/