Haskell: "instance (Enum a, Bounded a) => Random a" and "=> Произвольное a"
В Haskell Существует ли "стандартная" библиотека/пакет для генерации Random
/Arbitrary
перечислений?
Я написал следующий код, но я не могу поверить, что я первый человек, у которого есть эта необходимость или его решение (и я не уверен, что мое решение абсолютно правильно).
Кроме того, я надеюсь, что существующее решение имеет и другие приятные функции.
Здесь пара функций для выбора случайного значения из типа Enum:
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
type
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.)
Есть ли лучший способ? Не удается ли решить мое решение для некоторых (более "экзотических" ) ограниченных типов Enum, чем мой простой пример?
Ответы
Ответ 1
Стандартное обходное решение в подобных ситуациях заключается в создании обертки 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
Ответ 2
QuickCheck экспортирует функцию
arbitraryBoundedEnum :: (Bounded a, Enum a) => Gen a
Эту функцию можно разумно считать "стандартной".
Ответ 3
Нельзя объявлять такие экземпляры для любого типа Enum
. Причиной этого является то, что toEnum . fromEnum
не гарантированно ведет себя как id
. Возьмите экземпляр Enum
для Double
, например; функция fromEnum
просто возвращает "усеченное" интегральное значение двойника. Эти "более экзотические" типы (как вы их называете) не смогут работать с вашим решением.
Вот почему в целом разумно создавать экземпляры Random
для конкретных типов вместо этого и вообще избегать общих заявлений.
Обязательные расширения, которые вы указали, обязательно необходимы, если вы действительно хотите объявить указанный вами экземпляр, поскольку он является сигнатурой самого объявления экземпляра, который их требует.
Ответ 4
Еще одна причина не делать ваши экземпляры "универсальными": кто-то, кто хочет чаще отражать значения "реального мира" и, следовательно, хочет создать пользовательский экземпляр Arbitrary
с разными весами.
(Тем не менее, я использовал и определил вспомогательную функцию для записи экземпляра Arbitrary
в моем собственном коде, чтобы избежать повторения его для каждого небольшого типа.)