A bit late to the party, but this is the instance you're looking for in the current implementation.
instance (Arbitrary a, Show a, Testable prop) => Testable (a -> prop) where
property f =
propertyForAllShrinkShow arbitrary shrink (return . show) f
propertyForAllShrinkShow gen shr shw f =
-- gen :: Gen b, shr :: b -> [b], f :: b -> a -> prop
-- Idea: Generate and shrink (b, a) as a pair
propertyForAllShrinkShow
(liftM2 (,) gen arbitrary)
(liftShrink2 shr shrink)
(\(x, y) -> shw x ++ [show y])
(uncurry f)
As @chi correctly stated, there is recursion happening here. The recursive call is propertyForAllShrinkShow calling propertyForAllShrinkShow, and by calling uncurry, a property in the form a -> b -> c -> Bool gets turned into (a, b) -> c -> Bool. As (a, b) is a valid arbitrary because there is an instance of Arbitrary (a, b), the same instance of Testable will run again where prop is c -> Bool. Then, the same will run again as ((a, b), c, and prop will then be just Bool. At that point, the Bool instance of Testable kicks in, which uses the default propertyForAllShrinkShow, which creates the actual application of f x. So another way to say it is that quickcheck generates all of the values at the same time as an arbitrary tuple and uses recursion in the instance of Testable to construct the tuple.
Arbitrary. - Willem Van Onsem