在Haskell中,是否有用于生成Random
/ Arbitrary
枚举的“标准”库/包
我编写了以下代码,但我无法相信我是第一个有这种需求或解决它的人(而且我不确定我的解决方案是完全正确的)。 此外,我希望现有的解决方案还有其他不错的功能。
这是一对从枚举类型中选择随机值的函数:
enumRandomR :: (RandomGen g, Enum e) => (e, e) -> g -> (e, g)
enumRandomR (lo,hi) gen =
let (int, gen') = randomR (fromEnum lo, fromEnum hi) gen in (toEnum int, gen')
enumRandom :: (RandomGen g, Enum e) => g -> (e, g)
enumRandom gen =
let (int, gen') = random gen in (toEnum int, gen')
以下是System.Random.Random
和Test.QuickCheck.Arbitrary
{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances #-}
instance (Enum a, Bounded a) => Random a where
random = enumRandom
randomR = enumRandomR
instance (Enum a, Bounded a) => Arbitrary a where
arbitrary = choose (minBound, maxBound)
以下是Bounded
,Enum
类型
data Dir = N | E | S | W
deriving (Show, Enum, Bounded)
这是对随机/任意方法的测试
> import Test.QuickCheck
> sample (arbitrary:: Gen Dir)
N
E
N
S
N
E
W
N
N
W
W
我很高兴我的解决方案依赖于这些扩展:
{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances #-}"
,因为:
- Constraint is no smaller than the instance head
in the constraint: Enum a
(Use -XUndecidableInstances to permit this)
,
- Overlapping instances for Random Int
arising from a use of `randomR'
Matching instances:
instance Random Int -- Defined in System.Random
instance (Enum a, Bounded a) => Random a
和
- Illegal instance declaration for `Random a'
(All instance types must be of the form (T a1 ... an)
where a1 ... an are *distinct type variables*,
and each type variable appears at most once in the instance head.
Use -XFlexibleInstances if you want to disable this.)
有更好的方法吗?我的解决方案是否因某些(更“异乎寻常的”)有界枚举类型而失败,而不是我的简单示例?
答案 0 :(得分:10)
在这种情况下的标准解决方法是创建一个newtype
包装器并为其提供实例。
{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- avoid some boilerplate
newtype Enum' a = Enum' a
deriving (Bounded, Enum, Show)
instance (Enum a, Bounded a) => Random (Enum' a) where
random = enumRandom
randomR = enumRandomR
instance (Enum a, Bounded a) => Arbitrary (Enum' a) where
arbitrary = choose (minBound, maxBound)
当然,这种方法在使用新类型时需要一些额外的包装和展开,但是与QuickCheck一起使用时,这不应该太糟糕,因为您通常只需要为每个属性进行一次匹配模式:
prop_foo (Enum' x) = ... -- use x as before here
答案 1 :(得分:9)
QuickCheck导出函数
arbitraryBoundedEnum :: (Bounded a, Enum a) => Gen a
此功能可合理地被视为“标准”。
答案 2 :(得分:7)
为任何Enum
类型声明此类实例是不安全的。原因是toEnum . fromEnum
不能保证行为像id
。以Enum
的{{1}}实例为例; Double
函数只返回double的“截断”整数值。这些“更具异国情调”的类型(如您所说)将无法使用您的解决方案。
这就是为什么通常明智地为具体类型创建fromEnum
实例,并完全避免这样的一般声明。
如果您确实要声明所声明的实例,则必须要求您列出的扩展名,因为它是实例声明本身的签名,需要它们。
答案 3 :(得分:6)
不使您的实例“普遍”的另一个原因:想要更频繁地反映“真实世界”值的人,因此需要具有不同权重的自定义Arbitrary
实例。
(也就是说,我已经使用并定义了一个辅助函数,用于在我自己的代码中编写Arbitrary
实例,以避免为每一个小类型重复它。)