为什么我们不能为 Haskell 中的枚举派生随机 class 实例?
Why can't we have Random class instances derived for enumerations in Haskell?
我今天写了这个:
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中(实际上是在Haskell中扩展FlexibleInstances
),实例匹配是通过匹配类型来完成的,而不考虑约束。 类型匹配成功后,将检查约束,如果不满足约束将产生错误。所以,如果你定义:
instance (Bounded a, Enum a) => Random a where ...
您实际上是在为每个类型 a
定义一个实例,而不仅仅是具有 Bounded
和 Enum
实例的类型 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
扩展来允许对实例的限制。很好,但是如果您添加它,您将看到您需要 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
和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
在您自己的代码中。
我今天写了这个:
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中(实际上是在Haskell中扩展FlexibleInstances
),实例匹配是通过匹配类型来完成的,而不考虑约束。 类型匹配成功后,将检查约束,如果不满足约束将产生错误。所以,如果你定义:
instance (Bounded a, Enum a) => Random a where ...
您实际上是在为每个类型 a
定义一个实例,而不仅仅是具有 Bounded
和 Enum
实例的类型 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
扩展来允许对实例的限制。很好,但是如果您添加它,您将看到您需要 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
和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
在您自己的代码中。