Bodigrim/smallcheck

How to generate a non-empty list of items with a predicate (using suchThat)?

runeksvendsen opened this issue · 2 comments

I've copied suchThat: https://github.com/feuerbach/smallcheck/blob/8630db2a59aa942a4ac25fb42b6f957c1fb1fca3/Test/SmallCheck/Series.hs#L231

into my own codebase, since it isn't exported by SmallCheck, but is really useful.

I'm having some problems using it to generate a non-empty list, however, since I end up with really inefficient code:

import Test.SmallCheck.Series
import qualified Test.SmallCheck.Series as SS

instance Serial m (OrderBook venue base quote) where
   series = do
      midPrice <- series
      let buyOrderProp o  = oPrice (buyOrder o) < midPrice
          sellOrderProp o = oPrice (sellOrder o) > midPrice
      SS.NonEmpty buyOrders  <- series `suchThat` (all buyOrderProp . SS.getNonEmpty)
      SS.NonEmpty sellOrders <- series `suchThat` (all sellOrderProp . SS.getNonEmpty)
      return $ OrderBook (Vec.fromList $ sort buyOrders)
                         (Vec.fromList $ sort sellOrders)

When depth gets greater than 5, tests grind to a halt since any list which contains just a single item that doesn't fulfill the predicate is discarded.

Is there a way to use suchThat in combination with NonEmpty to generate a non-empty list of items that all adhere to a predicate on a per-item basis (as opposed to applying suchThat to the entire list)?

Side note: when using the above series implementation, SmallCheck fails to find counterexamples at depth 5, even though it successfully finds counterexamples at the same depth when using a more performant series implementation (which uses Test.SmallCheck.Series.list in combination with suchThat (although this doesn't work for non-empty lists)).

Sorry, I won't be able to help you — I just don't have the time. I suggest you come up with a working example and ask on stackoverflow or a similar venue.

For the record, I found a solution that performs better, which is simply:

nonEmptyList :: Series Identity a -> Series m [a]
nonEmptyList series = do
   depth <- getDepth
   return (depth `SS.list` series) `suchThat` (not . null)

and then you can apply your predicate to the individual items by using suchThat on the Series Identity a passed to nonEmptyList.