1

Given

data MyType = MyType ...

makeMyType :: String -> String -> String -> MyType
-- ...

type StringThing = String

where the strings that makeMyType expects are (respectively):

  • a - delimited string of some custom strings (e.g., "Hilary-Jeb-David-Charles"),
  • a string of 4 capital letters, and
  • a . delimited string of integers between 1 and 26, each padded with a zero to two characters (e.g., "04.23.11.09")

I can use QuickCheck to generate adequate test cases with something like

{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
import Test.QuickCheck
import Data.List (intercalate)
import Text.Printf (printf)

-- This really should be an arbitrary string of random length in some range
instance Arbitrary StringThing where
        arbitrary = elements ["FUSHFJSHF","KLSJDHFLSKJDHFLSKJDFHLSKJDHFLSKJOIWURURW","GHSHDHUUUHHHA"]

instance Arbitrary MyType where
        arbitrary = do
                -- This repetition feels unnecessary
                w1 <- elements ['A'..'Z']
                w2 <- elements ['A'..'Z']
                w3 <- elements ['A'..'Z']
                w4 <- elements ['A'..'Z']
                c1 <- elements someListOfCustomStuff
                c2 <- elements someListOfCustomStuff
                c3 <- elements someListOfCustomStuff
                c4 <- elements someListOfCustomStuff
                r1 <- choose (1,26)
                r2 <- choose (1,26)
                r3 <- choose (1,26)
                r4 <- choose (1,26)
                return $ makeMyType (intercalate "-" [c4,c3,c2,c1])
                                    [w1,w2,w3,w4]
                                    (intercalate "." $ (printf "%02d") <$> ([r1,r2,r3,r4] :: [Int]))

prop_SomeProp :: MyType -> StringThing -> Bool
prop_SomeProp mt st = ...

But StringThing really should take on arbitrary strings of capital letters of random length within some range, and the repetition of the same specification for all the w...s, c..s, and r....s seems unnecessary.

Is there a way in QuickCheck to:

  1. generate random strings with length within some bounds, restricted to certain characters, and
  2. "share" a specification using elements or choose among multiple values?
orome
  • 45,163
  • 57
  • 202
  • 418
  • in order to generate `[w1, ..., w4]`, you may use [`vectorOf 4 (elements ['A'..'Z'])`](https://hackage.haskell.org/package/QuickCheck-2.8.1/docs/Test-QuickCheck.html#v:vectorOf) instead of `w1 <- ...` – behzad.nouri Nov 11 '15 at 21:15
  • `vectorOf` is a synonym for `replicateM` which is the more general combinator. Not sure how it snuck in (does it date from a time before QC had a `Monad` instance?) – drquicksilver Nov 11 '15 at 21:25

1 Answers1

4

Yes. Haskell is great at being able to factor things out! You can certainly name and share subexpressions like elements ['A'..'Z']

capitals = elements ['A'..'Z']

instance Arbitrary StringThing where
        arbitrary = do
          l <- choose (1,50) -- this is your string length
          replicateM l capitals

And for your MyType, you can also use replicateM quite a lot:

instance Arbitrary MyType where
        arbitrary = do
                ws <- replicateM 4 capitals
                cs <- replicateM 4 (elements someListOfCustomStuff)
                rs <- replicateM 4 (choose (1,26))
                return $ makeMyType (intercalate "-" cs)
                                    ws
                                    (intercalate "." $ (printf "%02d") <$> (rs :: [Int]))
drquicksilver
  • 1,627
  • 9
  • 12