haskell-hedgehog
haskell-hedgehog copied to clipboard
Proposal: port of quickcheck's within, total, Gen.tuple2
I was thinking of porting these functions from QuickCheck over to Hedgehog, and wanted to know what the maintainers think first.
Hedgehog module:
-- A port of quickcheck's `total`
-- Similar to hedgehog's `eval`, but evaluates to NF instead of WHNF
-- https://hackage.haskell.org/package/QuickCheck-2.14/docs/Test-QuickCheck.html#v:total
evalNF :: (MonadTest m, NFData a, HasCallStack) => a -> m a
-- A port of quickcheck's `within`
-- https://hackage.haskell.org/package/QuickCheck-2.14/docs/Test-QuickCheck.html#v:within
within :: (MonadTest m, HasCallStack) => Int -> m a -> m a
Hedgehog.Gen module:
tuple2 :: MonadGen m => m a -> m b -> m (a, b)
tuple3 :: MonadGen m => m a -> m b -> m c -> m (a, b, c)
-- All the way up to tuple10, possibly?
Thank you, @dcastro :rocket: Let's do the within one first, if you wish :eyes: — On the others, for example, the tuples are trivial, so perhaps not really worth the effort(?)
tuple2 = liftM2 (,)
tuple3 = liftM3 (,,)
And for the total one perhaps we need a plan, e.g. should we port it as-is
total x = eval (rnf x)
or perhaps consider @sjakobi's suggestion in https://github.com/nick8325/quickcheck/issues/265, plus we might need to pickup a better name for it.
I've never had to use QC's within, and total, so others suggestions are more than welcome.
tuples
Ah, I keep forgetting about liftA/M :sweat_smile: Yea I agree, not worth the effort then!
total
Good point, we should definitely give the user a better error message. Maybe catch the exception and use something similar to Hedgehog.Internal.failException? E.g.
total :: (MonadTest m, NFData a, HasCallStack) => a -> m ()
total x =
either (withFrozenCallStack catchException) pure (tryEvaluate (rnf x))
where
catchException (SomeException ex) =
failWith Nothing $ unlines [
"━━━ Value was not total ━━━"
, "━━━ Exception (" ++ show (typeOf ex) ++ ") ━━━"
, List.dropWhileEnd Char.isSpace (displayException ex)
]
within
I'll have a go at implementing within. So far I've only been able to implement it in a way that it takes an IO a:
within :: (MonadTest m, MonadIO m, HasCallStack) => Int -> IO a -> m a
within microseconds io = withFrozenCallStack $
liftIO (timeout microseconds io) >>= \case
Just x -> return x
Nothing -> failWith Nothing $
"━━━ Operation did not complete within " ++ show microseconds ++ " microseconds ━━━"
But ideally, I would like it to take a generalized MonadTest m => m a as an input instead, so that it could be used with other combinators.
within :: (MonadTest m, MonadIO m, HasCallStack) => Int -> m a -> m a
-- usage
property $ within 1000 $ total (f args)
I think I'll have to dive deeper into hedgehog in order to make this work (I'm assuming such an implementation is even possible :eyes: what do you think?). If I get stuck, I might post again here in search of guidance.
I think I solved within in https://github.com/hedgehogqa/haskell-hedgehog/issues/211
@HuwCampbell oh awesome! Do you want to go ahead and submit a PR with that? Otherwise I'm ok doing it too.
On a related topic, I'm not sure what the best name would be for total/evalNF...
On a related topic, I'm not sure what the best name would be for
total/evalNF...
I've thought about it a bit, but haven't come up with anything better than total or just nf.
@moodmosaic so far we have total, evalNF and nf on the table, do any of these sound reasonable? Personally, I think evalNF pairs well with the existing eval for whnf, but I'm ok with way.
@HuwCampbell How should we proceed regarding within? I wouldn't mind taking over if you don't have the time.
I can put something up.
@dcastro
so far we have
total,evalNFandnfon the table, do any of these sound reasonable?
Perhaps we should stick with the QC terminology for this one (total)? If not, perhaps something with a mix of eval and deepseq, e.g. force, deepeval, evaldeep 🤔
/cc @jacobstanley
@moodmosaic Let's go with total then :)
One more suggestion if I may, something similar to quickcheck's implication operator ( ==>):
imply :: Monad m => Bool -> PropertyT m a -> PropertyT m a
imply False _ = discard
imply True prop = prop
@moodmosaic Let's go with
totalthen :)
I discussed about this with @jacobstanley a few times now (naming can be so hard!)—evalNF (your first suggestion) would be better because it also matches with the NFData typeclass constraint. :rocket:
(I apologize for going back and forth on that one, it's just that once we release it, it's there forever...)
@dcastro, on imply/==>, have you tried guard?
@moodmosaic No problem at all, it's understandable :) Alright, I'll create a PR then.
On guard, I was under the false impression that it would fail the test instead of discarding it, but seems I was wrong :D that's good to know, thanks!