nick8325 / quickcheck

Automatic testing of Haskell programs.
Other
713 stars 119 forks source link

Add generating monotonic functions #348

Open brandon-leapyear opened 1 year ago

brandon-leapyear commented 1 year ago

I was able to implement an arbitrary monotonic function like this:

-- | A modifier to generate monotonic functions
newtype Monotonic a b = Monotonic (a -> b)

instance Show (Monotonic a b) where
  show _ = "<monotonic fun>"

instance (Show b, Real a, Arbitrary b, Fractional b, Real b, Ord b) => Arbitrary (Monotonic a b) where
  arbitrary = do
    x <- arbitrary
    ups <- infiniteListMonotonic
    downs <- infiniteListMonotonic
    pure $ Monotonic $ \a ->
      let (n, frac) = properFraction $ toRational a
          (getIndex, index) =
            if n >= 0
              then (\i -> x + (ups !! i), n)
              else (\i -> x - (downs !! i), (-n))
          lo = getIndex index
          hi = getIndex (index + 1)
       in lo + fromRational frac * (hi - lo)
    where
      infiniteListMonotonic = scanl1 (+) . map getNonNegative <$> infiniteList

It would be great if this could be included in the base library, potentially with extra features like:

  1. Shrinking, the same way Fun does
  2. Make a new type class to support outputs that are integral or non-numerical
    • e.g. for Double -> Int, interpolate between the two ints, then round or truncate
  3. Make a new type class to support inputs that are still orderable, but non-numerical

    • e.g.
      
      data Octal = O0 | O1 | O2 | O3 | O4 | O5 | O6 | O7

    arbitrary :: Gen (Octal -> Int)

  4. More arbitrary interpolation
    • In this snippet, fractional indices do a linear interpolation between the two arguments, which is sufficient for my (most?) purposes