Создайте случайное значение из пользовательского типа данных в Haskell

Аналогично: Haskell Random from Datatype

Я создал тип данных, чтобы содержать различные виды оружия в игре Rock Paper Scissor.

data Weapon = Rock | Paper | Scissor

Теперь я хотел бы создать случайное оружие, которое будет использоваться компьютером против пользователя. Я взглянул на аналогичную ссылку, которую я опубликовал в начале, но для меня это кажется слишком общим.

Я могу генерировать случайные числа из любого другого типа. То, что я могу понять, - это сделать мой тип данных экземпляром класса Random.

Ответ 1

Чтобы создать случайный Weapon, внесите ли вы Weapon экземпляр Random или нет, вам нужен способ сопоставления чисел с Weapon s. Если вы выберете Enum для типа, то карта в и из Int определяется компилятором. Таким образом, вы можете определить

randomWeapon :: RandomGen g => g -> (Weapon, g)
randomWeapon g = case randomR (0,2) g of
                   (r, g') -> (toEnum r, g')

например. С экземпляром Enum вы также можете легко сделать Weapon экземпляр Random:

instance Random Weapon where
    random g = case randomR (0,2) g of
                 (r, g') -> (toEnum r, g')
    randomR (a,b) g = case randomR (fromEnum a, fromEnum b) g of
                        (r, g') -> (toEnum r, g')

Если есть возможность добавлять или удалять конструкторы из этого типа, лучший способ сохранить границы для randomR в синхронизации с типом - также выводить Bounded, как Joachim Breitner сразу предложил:

data Weapon
    = Rock
    | Paper
    | Scissors
      deriving (Bounded, Enum)

instance Random Weapon where
    random g = case randomR (fromEnum (minBound :: Weapon), fromEnum (maxBound :: Weapon)) g of
                 (r, g') -> (toEnum r, g')
    randomR (a,b) g = case randomR (fromEnum a, fromEnum b) g of
                        (r, g') -> (toEnum r, g')

Ответ 2

В то время как подход Дэниела Фишера Enum, безусловно, является хорошим общим способом сделать это, на самом деле не обязательно использовать явное отображение из Int s. Вы также можете просто

instance Random Weapon where
  random g = case random g of
               (r,g') | r < 1/3    = (Rock    , g')
                      | r < 2/3    = (Paper   , g')
                      | otherwise  = (Scissors, g')

используя экземпляр Double Random. Это менее эффективно, чем с производным экземпляром Enum, но более гибким - например, вы можете легко определить неравномерное распределение

  random g = case random g of
               (r,g') | r < 1/4    = (Rock    , g')
                      | r < 1/2    = (Paper   , g')
                      | otherwise  = (Scissors, g')

где Scissors более вероятно, чем два других. Конечно, вы должны делать такую ​​вещь, только если неравномерное распределение канонически для вашего типа данных, конечно, не в этом примере.

Ответ 3

{-# LANGUAGE FlexibleInstances, UndecidableInstances,
 ScopedTypeVariables, OverlappingInstances #-}

import System.Random

class (Bounded a, Enum a) => BoundedEnum a
instance (Bounded a, Enum a) => BoundedEnum a
instance BoundedEnum a => Random a where
   random gen = randomR (minBound :: a, maxBound :: a) gen
   randomR (f, t) gen =
     (toEnum r :: a, nextGen)
     where
       (rnd, nextGen) = next gen
       r = fromEnum f + (rnd `mod` length [f..t])

Теперь вы можете сказать:

r <- randomIO :: Anything

где Anything должно быть экземпляром классов Enum и Bounded.

Ответ 4

Если у вас есть генератор для данных, вы можете использовать oneof из Test.QuickCheck.Gen