1
votes

I have this test I want to make:

prop_inverse_stringsToInts st = isDigitList st ==> st == map show (stringsToInts st)

Which is testing a function that converts a list of Strings to a list of Integers, but of course the strings need to be digits so I created a pre-condition that checks that using the isDigitList function I made, but the condition is too specific and quickCheck gives up : "*** Gave up! Passed only 43 tests; 1000 discarded tests." So I wanted to create an Arbitrary instance for my case, but the thing is I am inexperienced with working with Arbitrary, so I don't really know how to do this and every time I shuffle code I get a new error. All I want is an Arbitrary that only returns the Foo [String] if it passes the isDigitList (which receives a [String] and returns a Bool). So far I have something like this :

  Foo a = Foo [String] deriving (Show,Eq)

  instance (Arbitrary a) => Arbitrary (Foo a ) where
           arbitrary = do 
                                st <- (arbitrary :: Gen [String])
                                if isDigitList st 
                                then do return (Foo st) 
                                else do return (Foo []) -- This is probably a bad idea

I altered my property to :

prop_inverse_stringsToInts :: Foo a -> Bool
prop_inverse_stringsToInts (Foo st) =  st == map show (stringsToInts st)

But now I am getting the error "* Ambiguous type variable a0' arising from a use of `quickCheck'" even though I am running quickCheck like this : > quickCheck (prop_inverse_stringsToInts :: Foo a -> Bool)

Can someone help please? Thank you in advance!

1
First things first, it looks like you don't need Foo to have a type argument. Try just making it `newtype Foo = Foo [String] deriving (Show, Eq). That should get rid of your "Ambiguous type variable" error.DDub
I'd remove the a parameter, what's its purpose? Also, your instance looks morally wrong: you should not simply discard bad lists, silencing the QuickCheck "gave up" message, you should generate good lists instead.chi
A much simpler method for testing might be to write prop_inverse_stringsToInts ints = ints == stringsToInts (map show ints). That is, instead of trying to generate Strings that can be converted to Ints and back, generate Ints, so that you know they can be converted.DDub
Yes it worked! Thanks guys, I thought I need that a parameter for the returns to work but I was wrong... And @chi what would you do to generate a good list instead? Thanks again!!!XplosiveFist
@XplosiveFist It looks like choose ('0','9') can generate one of the intended digits. Exploiting that you should be able to generate a list of surely-good digits. Right now I guess that 90%+ of the Foo you generate are empty lists, making testing 10x slower or more. In your own test only 43 cases (of 1000) were good, and Foo simply makes the other 957 "good" mapping them to the empty list. Alternatively, generate the integers instead, as done by DDub.chi

1 Answers

2
votes

It seems you know the basics, but I'll repeat them here just to be sure. There are two ways to get QuickCheck to generate the inputs you want:

  1. Have it generate some inputs and then filter out ones you don't want, or

  2. Have it generate only the inputs you want.

You started with option 1, but as you saw, that didn't work out great. Compared to all possible lists of String, there really aren't that many that are digit lists. The better option is to generate only the inputs you want.

To succeed at option 2, you need to make a generator, which would be a value of type Gen [String] that generates lists of Strings that fit your criteria. The generator you propose still uses the method of filtering, so you may want to try a different approach. Consider instead, something like:

genDigitStrings :: Gen [String]
genDigitStrings = do
  intList <- arbitrary :: Gen [Integer]
  return $ fmap show intList

This generator produces arbitrary lists of Strings that are always shown integers, meaning that they will always be digit lists. You can then go ahead and insert this into an Arbitrary instance for some newtype if you want.

For your own sanity, you can even check your work with a test like this:

propReallyActuallyDigitStrings = forAll genDigitStrings isDigitList

If that passes, you have some confidence that your generator really only produces digit lists, and if it fails, then you should adjust your generator.