packages feed

changeset-0.2.1: test/Main.hs

{-# OPTIONS_GHC -Wno-orphans #-}

module Main (main) where

-- base
import Control.Monad (replicateM_)
import Data.Char (toUpper)
import Data.Function ((&))
import Data.List.NonEmpty (NonEmpty (..))
import Data.Monoid (Last (Last), Product (..), Sum (..))
import Data.Proxy (Proxy (..))
import GHC.Generics (Generic)
import Prelude hiding (Foldable (..))

-- transformers
import Control.Monad.Trans.Reader (ReaderT (..), ask)

-- tasty
import Test.Tasty

-- tasty-hunit
import Test.Tasty.HUnit (testCase, (@?=))

-- falsify
import Test.Falsify.GenDefault (GenDefault (..), ViaGeneric (..))
import Test.Falsify.GenDefault.Std (Std)
import Test.Falsify.Generator (Gen)
import qualified Test.Falsify.Generator as Gen
import Test.Falsify.Predicate ((.$))
import qualified Test.Falsify.Predicate as P
import Test.Falsify.Range (between)
import Test.Tasty.Falsify (assert, gen, testProperty)

-- containers
import qualified Data.Map as M

-- changeset
import Control.Monad.Changeset.Class
import Control.Monad.Trans.Changeset
import Data.Monoid.RightAction (RightAction (..), RightTorsor (..), rEndo, set)
import Data.Monoid.RightAction.Coproduct (inL, inR, (:+:))
import Data.Monoid.RightAction.Generic (actRightGGeneric, actRightGeneric, differenceRightGGeneric, differenceRightGenericChanges)

type M = Changeset Int (Changes Count)
type MT = Changeset Torsor (Changes TorsorChange)

main :: IO ()
main =
  defaultMain $
    testGroup
      "changeset"
      [ testGroup
          "Changeset"
          [ testGroup
              "commutative monoids"
              [ testGroup
                  "Order of change and current matters"
                  [ testCase "change, current" $
                      evalChangeset (changeSingle Increment >> current) 0 @?= (1 :: Int)
                  , testCase "current, change" $
                      let action = flip evalChangeset 0 $ do
                            n <- current
                            changeSingle Increment
                            pure n
                       in action @?= (0 :: Int)
                  ]
              , testGroup
                  "execChangeset"
                  [ testCase "pure doesn't change state" $ execChangeset (pure () :: M ()) 0 @?= 0
                  , testCase "change changes state" $ execChangeset (changeSingle Increment :: M ()) 0 @?= 1
                  ]
              ]
          , testGroup
              "noncommutative monoids"
              [ testGroup
                  "Changes"
                  [ testCase "change is monoid homomorphism" $ do
                      execChangeset (changeSingle (Cons True) >> changeSingle (Cons False)) [] @?= execChangeset (change (singleChange (Cons True) <> singleChange (Cons False))) ([] :: [Bool])
                      execChangeset (changeSingle (Cons True) >> changeSingle (Cons False)) [] @?= execChangeset (change (addChange (Cons False) (singleChange (Cons True)))) ([] :: [Bool])
                      execChangeset (changeSingle (Cons True) >> changeSingle (Cons False)) [] @?= execChangeset (change (changes [Cons True, Cons False])) ([] :: [Bool])
                      execChangeset (changeSingle (Cons True) >> changeSingle (Cons False)) [] @?= ([False, True] :: [Bool])
                  , testCase "execChangeset is monoid homomorphism" $
                      execChangeset (changeSingle (Cons True) >> changeSingle (Cons False)) [] @?= (([] :: [Bool]) & execChangeset (changeSingle (Cons True)) & execChangeset (changeSingle (Cons False)))
                  ]
              ]
          ]
      , testGroup
          "RightAction"
          [ testCase "Changes is lawful monoid action" $ do
              [] `actRight` singleChange (Cons True) `actRight` singleChange (Cons False) @?= ([] :: [Bool]) `actRight` singleChange (Cons True) <> singleChange (Cons False)
          , testGroup "Last" $
              rightActionMonoidLaws @Int @(Last Int) genInt genLastInt
          , -- RightTorsor (Last s) s does not satisfy differenceRight . actRight = id:
            -- when the action sets the value to the same state, differenceRight returns mempty.
            testGroup "Sum" $
              rightActionMonoidLaws genSumInt genSumInt
          , testGroup "Product" $
              rightActionMonoidLaws (genProduct genNonZeroRational) (genProduct genNonZeroRational)
          , testGroup "()" $
              rightActionMonoidLaws @Int @() genInt (pure ())
          , -- RightTorsor () s cannot satisfy actRight sOrig (differenceRight sOrig sActed) = sActed
            -- when sOrig /= sActed, since () carries no information.
            testGroup "Maybe" $
              rightActionMonoidLaws @Int @(Maybe (Last Int)) genInt (genMaybe genLastInt)
          , testGroup "Tuple" $
              rightActionMonoidLaws @(Int, Int) @(Last Int, Last Int)
                ((,) <$> genInt <*> genInt)
                ((,) <$> genLastInt <*> genLastInt)
          ]
      , testGroup
          "RightTorsor"
          [ testGroup "Sum" $
              allLaws genSumInt genSumInt
          , testGroup "Product" $
              allLaws (genProduct genNonZeroRational) (genProduct genNonZeroRational)
          ]
      , testGroup
          "MonadChangeset"
          [ testCase "ReaderT lifts changeset operations" $
              let action = flip execChangeset (0 :: Int) $ flip runReaderT (100 :: Int) $ do
                    env <- ask
                    replicateM_ env $ changeSingle Increment
               in action @?= 100
          ]
      , testGroup "SetTo" $
          rightActionSemigroupLaw @Int @(SetTo Int) genInt genSetTo
            : rightTorsorLaws genInt genSetTo
      , testGroup
          "Changes"
          [ testGroup "Count" $
              rightActionMonoidLaws @Int @(Changes Count) genInt (genChanges genCount)
          , testGroup "ListChange" $
              rightActionMonoidLaws @[Int] @(Changes (ListChange Int))
                (genSmallList genInt)
                (genChanges genListChangeInt)
          ]
      , testGroup
          "MaybeChange"
          [ testGroup "RightAction" $
              rightActionMonoidLaws @(Maybe Int) @(MaybeChange Int)
                genMaybeInt
                (genMaybeChange genInt)
                -- RightTorsor (MaybeChange a) (Maybe a) does not satisfy differenceRight . actRight = id:
                -- when the action sets the value to the same state, differenceRight returns mempty.
          ]
      , testGroup
          "FmapChange"
          [ testGroup "RightAction" $
              rightActionMonoidLaws @(Maybe Int) @(FmapChange Maybe (Last Int))
                genMaybeInt
                (genFmapChange genLastInt)
          ]
      , testGroup
          "diff and update"
          [ testProperty "diff yields differenceRight from current" $ do
              s0 <- gen genTorsor
              s1 <- gen genTorsor
              assert $
                P.eq
                  .$ ("snd (getChangeset (diff s1) s0)", snd (getChangeset (diff s1 :: MT (Changes TorsorChange)) s0))
                  .$ ("differenceRight s0 s1", differenceRight s0 s1)
          , testProperty "update sets the state" $ do
              s0 <- gen genTorsor
              s1 <- gen genTorsor
              assert $
                P.eq
                  .$ ("execChangeset (update s1) s0", execChangeset (update s1 :: MT ()) s0)
                  .$ ("s1", s1)
          , testProperty "change after update to same state is mempty" $ do
              s <- gen genTorsor
              assert $
                P.expect mempty
                  .$ ("getChange (update s) s", getChange (update s :: MT ()) s)
          ]
      , testGroup
          "Coproduct"
          [ testCase ":+: is monoid morphism" $
              (0 :: Int) `actRight` (inL (Last (Just 1)) <> inL (Last (Just 2)) :: Last Int :+: Last Int) @?= 0 `actRight` (inL (Last (Just (1 :: Int)) <> Last (Just 2)) :: Last Int :+: Last Int)
          , testGroup "RightAction" $
              rightActionMonoidLaws @Int @(Last Int :+: Last Int)
                genInt
                (Gen.choose (inL <$> genLastInt) (inR <$> genLastInt))
          ]
      , testGroup
          "FilterableChange"
          [ testCase "Can change a map with FilterableChange" $
              M.fromList [(0 :: Int, "hello"), (1, "world")] `actRight` FilterableChange (justChange $ ImapChange (\i -> if i == 2 then rEndo toUpper else mempty))
                @?= M.fromList [(0 :: Int, "heLlo"), (1, "woRld")]
          ]
      , testGroup
          "FilterableChanges"
          [ testCase "Can change a map depending on content" $
              M.fromList [(0 :: Int, "hello"), (1, "world"), (2, "!")]
                `actRight` FilterableChanges
                  ( \case
                      "hello" -> FilterablePositionChange (set "hi")
                      "!" -> FilterablePositionDelete
                      _ -> mempty
                  )
                @?= M.fromList [(0 :: Int, "hi"), (1, "world")]
          ]
      , testGroup
          "AlignChanges"
          [ testCase "Can change a map with another map" $
              M.fromList
                [ (0 :: Int, "hello")
                , (1, "world")
                , (2, "!")
                ]
                `actRight` AlignChanges
                  ( M.fromList
                      [ (0 :: Int, SetAlignPosition "hi")
                      , (1, ChangeAlignPosition $ FmapChange @[] $ rEndo toUpper)
                      , (2, DeleteAlignPosition)
                      , (3, SetAlignPosition "...")
                      ]
                  )
                @?= M.fromList [(0 :: Int, "hi"), (1, "WORLD"), (3, "...")]
          , testGroup "RightAction" $
              rightActionMonoidLaws @(M.Map Int (Sum Int)) @(AlignChanges (M.Map Int) (Sum Int) (Sum Int))
                genMapIntInt
                (genAlignChangesMap (genDefault (Proxy @Std)))
          , testGroup
              "RightTorsor"
              [ -- The differenceRight . actRight law does not hold for AlignChanges:
                -- ChangeAlignPosition w on an unchanged entry round-trips to ChangeAlignPosition mempty ≠ w.
                -- Only actRight . differenceRight (law 2) holds.
                testProperty "actRight . differenceRight" $ do
                  sOrig <- gen genMapIntInt
                  sActed <- gen genMapIntInt
                  assert $
                    P.eq
                      .$ ("sOrig `actRight` differenceRight sOrig sActed", sOrig `actRight` differenceRight @(AlignChanges (M.Map Int) (Sum Int) (Sum Int)) sOrig sActed)
                      .$ ("sActed", sActed)
              ]
          ]
      , testGroup
          "Generic"
          [ testGroup
              "actRightGeneric"
              [ testCase "single change left" $ 0 `actRight` IntActionCounts Increment mempty @?= (1 :: Int)
              , testCase "single change product" $ 0 `actRight` IntActionCounts Increment (singleChange Increment) @?= (2 :: Int)
              , testCase "single change right" $ 0 `actRight` IntActionLast (set 100) @?= (100 :: Int)
              , testCase "single change recursive" $ 0 `actRight` IntActionRec (IntActionLast (set 100)) (IntActionCounts Increment mempty) @?= (101 :: Int)
              , testCase "changes" $
                  (0 :: Int)
                    `actRight` changes
                      [ IntActionCounts Increment mempty
                      , IntActionLast (set 10)
                      , IntActionCounts Increment (changes [Increment, Increment])
                      , IntActionRec (IntActionRec (IntActionCounts Increment mempty) (IntActionCounts Increment mempty)) (IntActionCounts Increment (changes [Increment, Increment]))
                      ]
                    @?= 18
              ]
          , testGroup
              "actRightGGeneric"
              [ testCase "Wrapper changes wrapper" $ Bar 0 `actRight` BarChange Increment @?= Bar 1
              , testCase "Sum changes product" $ Foo 0 0 [] `actRight` changes [FooChangeInt Increment, FooChangeList (Cons ()), FooChangeInt2 Increment] @?= Foo 1 1 [()]
              , testCase "Product changes product" $ Foo 0 0 [] `actRight` FooChange2 Increment Nothing (Cons ()) @?= Foo 1 0 [()]
              ]
          , testGroup
              "differenceRightGGeneric"
              [ testGroup "Wrapper" $ torsorTestSuite (TorsorWrapper 9) (TorsorWrapper 1) (TorsorWrapper 10)
              , testGroup
                  "Sum"
                  [ testGroup "All fields change" $ torsorTestSuite (changes [TorsorChangeSum 5, TorsorChangeProduct 6, TorsorChangeSum2 7]) (Torsor 2 3 4) (Torsor 7 18 11)
                  , testGroup "One field changes" $ torsorTestSuite (singleChange (TorsorChangeProduct 6)) (Torsor 2 3 4) (Torsor 2 18 4)
                  , testGroup "No fields change" $ torsorTestSuite (mempty :: Changes TorsorChange) (Torsor 2 3 4) (Torsor 2 3 4)
                  , testGroup "Property laws" $
                      changesLaws @Torsor @TorsorChange
                        (flip actRight)
                        differenceRight
                        genTorsor
                        genTorsorChange
                  ]
              , testGroup
                  "Product"
                  ( torsorTestSuite (TorsorChange2 5 6 7) (Torsor 2 3 4) (Torsor 7 18 11)
                      <> rightTorsorLaws @Torsor @TorsorChange2
                        genTorsor
                        genTorsorChange2
                  )
              , testGroup
                  "Newtype"
                  ( torsorTestSuite (TorsorWrapper 9) (TorsorWrapper 1) (TorsorWrapper 10)
                      <> rightTorsorLaws @TorsorWrapper @TorsorWrapper
                        genTorsorWrapper
                        genTorsorWrapper
                  )
              ]
          ]
      ]

data IntAction
  = IntActionCounts Count (Changes Count)
  | IntActionLast (Last Int)
  | IntActionRec IntAction IntAction
  deriving stock (Generic)

instance RightAction IntAction Int where
  actRight = actRightGeneric

newtype Bar = Bar Int
  deriving stock (Generic, Eq, Show)

newtype BarChange = BarChange Count
  deriving stock (Generic)

instance RightAction BarChange Bar where
  actRight = actRightGGeneric

data Foo = Foo Int Int [()]
  deriving stock (Generic, Eq, Show)

data FooChange = FooChangeInt Count | FooChangeInt2 Count | FooChangeList (ListChange ())
  deriving stock (Generic)

instance RightAction FooChange Foo where
  actRight = actRightGGeneric

data FooChange2 = FooChange2 Count (Maybe Count) (ListChange ())
  deriving stock (Generic)

instance RightAction FooChange2 Foo where
  actRight = actRightGGeneric

newtype TorsorWrapper = TorsorWrapper (Sum Integer)
  deriving stock (Generic, Eq, Show)

instance RightAction TorsorWrapper TorsorWrapper where
  actRight = actRightGGeneric

instance RightTorsor TorsorWrapper TorsorWrapper where
  differenceRight = differenceRightGGeneric

data Torsor = Torsor (Sum Integer) (Product Rational) (Sum Rational)
  deriving stock (Generic, Eq, Show)

data TorsorChange = TorsorChangeSum (Sum Integer) | TorsorChangeProduct (Product Rational) | TorsorChangeSum2 (Sum Rational)
  deriving stock (Generic, Eq, Show)

instance RightAction TorsorChange Torsor where
  actRight = actRightGGeneric

instance RightTorsor (Changes TorsorChange) Torsor where
  differenceRight = differenceRightGenericChanges

data TorsorChange2 = TorsorChange2 (Sum Integer) (Product Rational) (Sum Rational)
  deriving stock (Generic, Eq, Show)

instance RightAction TorsorChange2 Torsor where
  actRight = actRightGGeneric

instance RightTorsor TorsorChange2 Torsor where
  differenceRight = differenceRightGGeneric

-- * GenDefault instances via ViaGeneric Std

deriving via ViaGeneric Std Count instance GenDefault Std Count
deriving via ViaGeneric Std (Sum Int) instance GenDefault Std (Sum Int)
deriving via ViaGeneric Std (Last Int) instance GenDefault Std (Last Int)
deriving via ViaGeneric Std (SetTo Int) instance GenDefault Std (SetTo Int)
deriving via ViaGeneric Std (ListChange Int) instance GenDefault Std (ListChange Int)
deriving via ViaGeneric Std (AlignPositionChange (Sum Int) (Sum Int)) instance GenDefault Std (AlignPositionChange (Sum Int) (Sum Int))

torsorTestSuite :: forall w s. (RightAction w s, RightTorsor w s, Eq w, Show w, Eq s, Show s) => w -> s -> s -> [TestTree]
torsorTestSuite w sOrig sActed =
  [ testCase "act" $ sOrig `actRight` w @?= sActed
  , testCase "difference" $ sOrig `differenceRight` sActed @?= w
  , testCase "law 1" $ sOrig `differenceRight` (sOrig `actRight` w :: s) @?= w
  , testCase "law 2" $ sOrig `actRight` (sOrig `differenceRight` sActed :: w) @?= sActed
  ]

-- * Reusable law tests

-- | Tests for 'RightTorsor' laws that don't require 'Monoid' on the change type.
rightTorsorLaws :: forall s w. (Eq s, Show s, Eq w, Show w, RightAction w s, RightTorsor w s) => Gen s -> Gen w -> [TestTree]
rightTorsorLaws genS genW =
  [ rightTorsorLaw1 genS genW
  , rightTorsorLaw2 genS genW
  ]

rightTorsorLaw1 :: forall s w. (Eq s, Show s, Eq w, Show w, RightAction w s, RightTorsor w s) => Gen s -> Gen w -> TestTree
rightTorsorLaw1 genS genW = testProperty "differenceRight . actRight" $ do
  s <- gen genS
  w <- gen genW
  assert $
    P.eq
      .$ ("differenceRight s (s `actRight` w)", differenceRight s (s `actRight` w))
      .$ ("w", w)
rightTorsorLaw2 :: forall s w proxy. (Eq s, Show s, RightAction w s, RightTorsor w s) => Gen s -> proxy w -> TestTree
rightTorsorLaw2 genS _ = testProperty "actRight . differenceRight" $ do
  sOrig <- gen genS
  sActed <- gen genS
  assert $
    P.eq
      .$ ("sOrig `actRight` differenceRight sOrig sActed", sOrig `actRight` differenceRight @w sOrig sActed)
      .$ ("sActed", sActed)

-- | Tests for 'RightAction' laws that require 'Monoid' on the change type.
rightActionMonoidLaws :: forall s w. (Eq s, Show s, Eq w, Show w, Monoid w, RightAction w s) => Gen s -> Gen w -> [TestTree]
rightActionMonoidLaws genS genW = [rightActionSemigroupLaw genS genW, rightActionMemptyLaw genS genW]

rightActionSemigroupLaw :: forall s w. (Eq s, Show s, Eq w, Show w, Semigroup w, RightAction w s) => Gen s -> Gen w -> TestTree
rightActionSemigroupLaw genS genW =
  testProperty "actRight semigroup" $ do
    s <- gen genS
    m1 <- gen genW
    m2 <- gen genW
    assert $
      P.eq
        .$ ("(s `actRight` m1) `actRight` m2", (s `actRight` m1) `actRight` m2)
        .$ ("s `actRight` (m1 <> m2)", s `actRight` (m1 <> m2))

rightActionMemptyLaw :: forall s w proxy. (Eq s, Show s, Eq w, Show w, Monoid w, RightAction w s) => Gen s -> proxy w -> TestTree
rightActionMemptyLaw genS _ = testProperty "actRight mempty" $ do
  s <- gen genS
  assert $
    P.eq
      .$ ("s `actRight` mempty", s `actRight` (mempty @w))
      .$ ("s", s)

-- | Additional 'RightTorsor' law that requires 'Monoid': @differenceRight s s = mempty@.
rightTorsorMemptyLaw :: forall s w. (Eq s, Show s, Eq w, Show w, Monoid w, RightTorsor w s) => Gen s -> Gen w -> [TestTree]
rightTorsorMemptyLaw genS _genW =
  [ testProperty "differenceRight self = mempty" $ do
      s <- gen genS
      assert $
        P.expect (mempty @w)
          .$ ("differenceRight s s", differenceRight @w s s)
  ]

-- | All 'RightAction' and 'RightTorsor' laws for a 'Monoid' change type.
allLaws :: (Eq s, Show s, Eq w, Show w, Monoid w, RightAction w s, RightTorsor w s) => Gen s -> Gen w -> [TestTree]
allLaws genS genW = rightActionMonoidLaws genS genW <> (rightTorsorLaws genS genW <> rightTorsorMemptyLaw genS genW)

{- | All laws for the common pattern where @RightAction w s@ and @RightTorsor (Changes w) s@.

Note: The @differenceRight . actRight@ test checks that the round-tripped diff has the same /effect/
rather than being structurally equal, since 'Changes' is a free monoid and multiple representations
can encode the same action.
-}
changesLaws ::
  forall s w.
  (Eq s, Show s, Eq w, Show w) =>
  (Changes w -> s -> s) ->
  (s -> s -> Changes w) ->
  Gen s ->
  Gen w ->
  [TestTree]
changesLaws act diffFn genS genW =
  [ testProperty "differenceRight . actRight (effect)" $ do
      s <- gen genS
      w <- gen genChangesW
      -- The diff of the round-trip must produce the same effect, not necessarily the same Changes list
      assert $
        P.eq
          .$ ("act (differenceRight s (act w s)) s", act (diffFn s (act w s)) s)
          .$ ("act w s", act w s)
  , testProperty "actRight . differenceRight" $ do
      sOrig <- gen genS
      sActed <- gen genS
      assert $
        P.eq
          .$ ("act (differenceRight sOrig sActed) sOrig", act (diffFn sOrig sActed) sOrig)
          .$ ("sActed", sActed)
  , testProperty "differenceRight self = mempty" $ do
      s <- gen genS
      assert $
        P.expect mempty
          .$ ("differenceRight s s", diffFn s s)
  ]
  where
    genChangesW :: Gen (Changes w)
    genChangesW = genChanges genW

-- * Generators

genInt :: Gen Int
genInt = Gen.int (between (0, 100))

genSetTo :: Gen (SetTo Int)
genSetTo = genDefault (Proxy @Std)

-- | Generate a non-zero rational (for Product torsor, avoiding division by zero).
genNonZeroRational :: Gen Rational
genNonZeroRational = do
  n <- Gen.int (between (1, 100))
  d <- Gen.int (between (1, 100))
  sign <- Gen.bool True
  pure $ (if sign then 1 else -1) * fromIntegral n / fromIntegral d

genSumInt :: Gen (Sum Int)
genSumInt = genDefault (Proxy @Std)

genProduct :: Gen a -> Gen (Product a)
genProduct = fmap Product

genLastInt :: Gen (Last Int)
genLastInt = genDefault (Proxy @Std)

genMaybe :: Gen a -> Gen (Maybe a)
genMaybe = Gen.shrinkToNothing

genChanges :: Gen w -> Gen (Changes w)
genChanges genW = changes <$> Gen.list (between (0, 5)) genW

genListChangeInt :: Gen (ListChange Int)
genListChangeInt = genDefault (Proxy @Std)

genCount :: Gen Count
genCount = genDefault (Proxy @Std)

genMaybeChange :: Gen a -> Gen (MaybeChange a)
genMaybeChange genA = Gen.choose (pure mempty) (Gen.choose (setJust <$> genA) (pure setNothing))

genFmapChange :: Gen w -> Gen (FmapChange f w)
genFmapChange = fmap FmapChange

genMaybeInt :: Gen (Maybe Int)
genMaybeInt = genDefault (Proxy @Std)

genSmallList :: Gen a -> Gen [a]
genSmallList = Gen.list (between (0, 5))

genAlignChangesMap :: Gen (AlignPositionChange w s) -> Gen (AlignChanges (M.Map Int) w s)
genAlignChangesMap genAPC = do
  kvs <- Gen.list (between (0, 5)) $ do
    k <- Gen.int (between (0, 10))
    v <- genAPC
    pure (k, v)
  pure $ AlignChanges $ M.fromList kvs

genSmallMap :: (Ord k) => Gen k -> Gen v -> Gen (M.Map k v)
genSmallMap genK genV = do
  kvs <- Gen.list (between (0, 5)) ((,) <$> genK <*> genV)
  pure $ M.fromList kvs

genMapIntInt :: Gen (M.Map Int (Sum Int))
genMapIntInt = genSmallMap (Gen.int (between (0, 10))) genSumInt

genSumInteger :: Gen (Sum Integer)
genSumInteger = Sum . fromIntegral <$> Gen.int (between (-100, 100))

genProductRational :: Gen (Product Rational)
genProductRational = do
  n <- Gen.int (between (1, 100))
  d <- Gen.int (between (1, 100))
  sign <- Gen.bool True
  pure $ Product $ (if sign then 1 else -1) * fromIntegral n / fromIntegral d

genSumRational :: Gen (Sum Rational)
genSumRational = do
  n <- Gen.int (between (-100, 100))
  d <- Gen.int (between (1, 100))
  pure $ Sum $ fromIntegral n / fromIntegral d

genTorsorWrapper :: Gen TorsorWrapper
genTorsorWrapper = TorsorWrapper <$> genSumInteger

genTorsor :: Gen Torsor
genTorsor = Torsor <$> genSumInteger <*> genProductRational <*> genSumRational

genTorsorChange :: Gen TorsorChange
genTorsorChange =
  Gen.oneof
    ( (TorsorChangeSum <$> genSumInteger)
        :| [ TorsorChangeProduct <$> genProductRational
           , TorsorChangeSum2 <$> genSumRational
           ]
    )

genTorsorChange2 :: Gen TorsorChange2
genTorsorChange2 = TorsorChange2 <$> genSumInteger <*> genProductRational <*> genSumRational