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





Поскольку вы, кажется, хотите равномерно распределить между конструкторами 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