Использование выбора частоты Haskell QuickCheck

Итак, у меня есть код ниже, и я пытаюсь сделать его экземпляром Arbitrary:

data MyData = I Int | B Bool

instance Arbitrary MyData where
  arbitrary = do {
     frequency [(1, return (I 1)),
                (1, return (choose((B True), (B False))))]
  }

Однако при этом я получаю (понятно) ошибку:

Couldn't match type ‘Gen MyData’ with ‘MyData’
      Expected type: Gen MyData
        Actual type: Gen (Gen MyData)

Как я могу реализовать это? Также вместо (I 1) хотелось бы вернуть I случайным Int. Однако использование функции arbitrary вместо 1 приводит к той же ошибке.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
65
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Поскольку вы, кажется, хотите равномерно распределить между конструкторами I и B, более простым решением было бы использовать один из вместо frequency:

data MyData = I Int | B Bool deriving (Eq, Show)

instance Arbitrary MyData where
  arbitrary = oneof [genI, genB]
    where genI = fmap I arbitrary 
          genB = fmap B arbitrary

Генераторы genI и genB используют базовые Arbitrary экземпляры Int и Bool, сопоставляя необработанные целые числа и логические значения с соответствующими конструкторами case.

Вот набор примерных данных:

> sample (arbitrary :: Gen MyData)
B False
B False
I 2
B False
I 1
I 7
B False
B False
B True
I 7
B False

Как видите, он также позволяет выбирать произвольные целые числа.


Код в OP имеет несколько проблем. Первое сообщение об ошибке состоит в том, что возвращаемый тип является вложенным. Один из способов обойти это — удалить обозначение do. Это, однако, не решает проблему.

Даже если вы уменьшите его до следующего, он не проверяет тип:

instance Arbitrary MyData where
  arbitrary =
     frequency [(1, return (I 1)),
                (1, choose(B True, B False))]

Эта попытка приводит к ошибке:

Q72160684.hs:10:21: error:
    * No instance for (random-1.1:System.Random.Random MyData)
        arising from a use of `choose'
    * In the expression: choose (B True, B False)
      In the expression: (1, choose (B True, B False))
      In the first argument of `frequency', namely
        `[(1, return (I 1)), (1, choose (B True, B False))]'
   |
10 |                 (1, choose(B True, B False))]
   |                     ^^^^^^^^^^^^^^^^^^^^^^^

Метод choose требует, чтобы ввод был Random экземплярами, а MyData — нет.


Если вы действительно хотите использовать frequency, а не oneof, проще всего сначала заставить работать oneof, поскольку вы можете рассматривать frequency как обобщение oneof.

Во-первых, чтобы сделать код более лаконичным, я использовал <$> вместо fmap, а затем встроил оба генератора:

instance Arbitrary MyData where
  arbitrary = oneof [I <$> arbitrary, B <$> arbitrary]

Теперь замените oneof на frequency и измените каждый генератор на взвешенный кортеж:

instance Arbitrary MyData where
  arbitrary = frequency [(10, I <$> arbitrary), (1, B <$> arbitrary)]

Выборка из этого экземпляра показывает, что распределение теперь искажено:

> sample (arbitrary :: Gen MyData)
I 0
I (-2)
I (-4)
I (-1)
I 0
I 8
B True
I 1
I 3
I (-3)
I (-16)

Есть 10 значений I и только 1 значение B.

Вы можете получить его с помощью общий-случайный (с 1.5.0.0).

Получение через GenericArbitraryU: дает равномерное распределение (например, oneof из ответа Марка Симанна):

{-# Language DataKinds     #-}
{-# Language DeriveGeneriC#-}
{-# Language DerivingVia   #-}

import Test.QuickCheck
import GHC.Generics

import Generic.Random.DerivingVia

-- ghci> :set -XTypeApplications
-- ghci> sample @MyData arbitrary
-- I 0
-- I 1
-- B True
-- I 6
-- I (-5)
-- I (-7)
-- B True
-- I (-10)
-- B True
-- B True
-- I (-9)
data MyData = I Int | B Bool
  deriving
  stock (Show, Generic)

  deriving Arbitrary
  via GenericArbitraryU MyData

Получение через GenericArbitrary: дает взвешенное распределение, заданное списком чисел на уровне типа. Они обозначают частоту каждого конструктора (например, frequency):

-- ghci> sample @MyData arbitrary
-- I 0
-- I (-2)
-- I 4
-- I 5
-- I 2
-- I 0
-- B False
-- I (-9)
-- I (-10)
-- I (-3)
-- I (-8)
data MyData = I Int | B Bool
  deriving
  stock (Show, Generic)

  deriving Arbitrary
  via GenericArbitrary '[10, 1] MyData

Другие вопросы по теме