quickcheck icon indicating copy to clipboard operation
quickcheck copied to clipboard

Add generating monotonic functions

Open brandon-leapyear opened this issue 3 years ago • 0 comments

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

brandon-leapyear avatar Dec 07 '22 23:12 brandon-leapyear