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 в моем собственном коде, чтобы избежать повторения его для каждого небольшого типа.)