puzzled
puzzled

Reputation: 77

Haskell QuickCheck generating values within a function

How do I get this contrived example to work?

newtype Q = Q [Int]

instance Arbitrary Q where
    arbitrary :: Gen Q
    arbitrary = do
        len <- choose (1, 5)
        pure $ g len (\ i -> i + choose (0, 1)) -- want different choice for each invocation

g :: Int -> (Int -> Int) -> Q -- g is a library function
g len f = Q $ fmap f [1 .. len]

It gives compiler error:

    * Couldn't match expected type `Int' with actual type `Gen a0'
    * In the second argument of `(+)', namely `choose (0, 1)'
      In the expression: i + choose (0, 1)
      In the second argument of `g', namely `(\ i -> i + choose (0, 1))'

Upvotes: 0

Views: 202

Answers (1)

Neil Locketz
Neil Locketz

Reputation: 4318

The problem is choose (0, 1) does not produce an Int, it produces a Gen Int.

You're treating it as though it were an Int in this expression:

pure $ g (\i -> i + choose (0, 1))

Since Gen Int is a monad you need to bind it in order to use the result of the "choice".

Something like this:

instance Arbitrary Q where
    arbitrary :: Gen Q
    arbitrary = do
        choice <- choose (0, 1)
        return $ g (\i -> i + choice)

Responding to the edited question:

The issue is still the same, you're trying to use the Gen Int as though it were an Int. You can bind multiple times inside a do.

Here is a solution:

instance Arbitrary Q where
    arbitrary :: Gen Q
    arbitrary = do
        len <- choose (1, 5)
        choice <- choose (0, 1)
        return $ g len (\i -> i + choice)

Responding to the edited, edited question:

You have to propagate the side effects somewhere, this just means you need to run choose len times. Because g is a "library" function, I'm going to assume that you have no control over it, and can't change it. Note that the solution below is ugly since I need to use the partial function (!!), and because it is rather slow (there is probably a better way to do this, but I'm not able to find it).

The trick here is I'm mapping a function that returns len Gen Int's, and then runs all of them, producing a list of chosen numbers (see mapM description for more details).

instance Arbitrary Q where
 arbitrary :: Gen Q
 arbitrary = do
     len <- choose (1, 5)
     choices <- mapM (\_ -> choose (0, 1)) [1 .. len]
     return $ g len (\i -> i + (choices !! i))

Upvotes: 4

Related Questions