Когда мы используем QuickCheck для проверки наших программ, нам нужно определить генераторы для наших данных, есть некоторый общий способ их определения, но общий способ обычно становится бесполезным, когда нам нужны сгенерированные данные для удовлетворения некоторых ограничений для работы .
, например
data Expr
= LitI Int
| LitB Bool
| Add Expr Expr
| And Expr Expr
data TyRep = Number | Boolean
typeInfer :: Expr -> Maybe TyRep
typeInfer e = case e of
LitI _ -> Number
LitB _ -> Boolean
Add e1 e2 -> case (typeInfer e1, typeInfer e2) of
(Just Number, Just Number) -> Just Number
_ -> Nothing
And e1 e2 -> case (typeInfer e1, typeInfer e2) of
(Just Boolean, Just Boolean) -> Just Boolean
_ -> Nothing
Теперь мне нужно определить генератор Expr (т.е. Gen Expr
или instance Arbitrary Expr
), но также хочу, чтобы он генерировал корректные типы (т.е. isJust (typeInfer generatedExpr)
)
Наивный способ сделать это - использовать suchThat
, чтобы отфильтровать недействительные, но это, очевидно, неэффективно, когда Expr
и TyRep
усложняются с большим количеством случаев.
Другая похожая ситуация касается ссылочной целостности, например
data Expr
= LitI Int
| LitB Bool
| Add Expr Expr
| And Expr Expr
| Ref String -- ^ reference another Expr via it's name
type Context = Map String Expr
В этом случае мы хотим, чтобы все ссылочные имена в сгенерированном Expr
содержались в каком-то конкретном Context
, теперь мне нужно сгенерировать Expr
для конкретного Context
:
arbExpr :: Context -> Gen Expr
, но теперь проблема с усадкой будет проблемой, и чтобы решить эту проблему, я должен определить конкретную версию сжатия и использовать forAllShrink
каждый раз, когда я использую arbExpr
, что означает большую работу.
Итак, я хочу знать, есть ли лучшая практика для таких вещей?