haskell - 为什么我们不能在 Haskell 中为枚举派生 Random 类实例?

标签 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)

而且我认为这对于任何枚举来说都是大致可复制粘贴的。
我尝试将 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 ,而不仅仅是类型 aBoundedEnum实例。就好像你写过:
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 ,它永远不会发生。

同样,可以允许 RandomEnum 的任何类型自动派生和 Bounded ,但社区不愿意添加额外的自动派生机制,特别是对于像 Random 这样的类型类。只是不经常使用(与说 ShowEq 相比)。所以,再一次,它永远不会发生。

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

相关文章:

haskell - 保证类型家族会派生某些类

c++ - char 初始化枚举的基础类型应该是什么?

c++ - 编译错误 - 类中的枚举

mysql - 使用 hibernate 将枚举存储在数据库中

haskell - 实例电感作为约束

mysql - "Not a PersistText value"是什么意思?

haskell - 在 Haskell 中, "higher-kinded types"*真的*类型吗?或者它们仅仅表示*具体*类型的集合而仅此而已?

haskell - 范畴论基础

haskell - GADT 上的模式匹配

scala - 类型类中的多个类型参数