packages feed

changeset 0.2 → 0.2.1

raw patch · 7 files changed

+442/−59 lines, 7 filesdep +falsifyPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: falsify

API changes (from Hackage documentation)

- Data.Monoid.RightAction: instance Data.Monoid.RightAction.RightAction m ()
+ Control.Monad.Changeset.Class: update :: (MonadChangeset s w m, RightTorsor w s) => s -> m ()
+ Control.Monad.Trans.Changeset: instance GHC.Base.Semigroup (Control.Monad.Trans.Changeset.SetTo a)
+ Data.Monoid.RightAction.Coproduct: instance (GHC.Show.Show m, GHC.Show.Show n, GHC.Base.Semigroup m, GHC.Base.Semigroup n) => GHC.Show.Show (m Data.Monoid.RightAction.Coproduct.:+: n)

Files

changeset.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: changeset-version: 0.2+version: 0.2.1 synopsis: Stateful monad transformer based on monoidal actions description:   A general state monad transformer with separate types for the state and the possible changes.@@ -47,6 +47,7 @@     DeriveGeneric     DeriveTraversable     DerivingStrategies+    DerivingVia     FlexibleContexts     FlexibleInstances     FunctionalDependencies@@ -113,6 +114,7 @@     base,     changeset,     containers >=0.6 && <0.8,+    falsify ^>=0.2.0,     monoid-extras,     tasty >=1.4.2 && <1.6,     tasty-hunit ^>=0.10.2,
examples/Control/Monad/Trans/Changeset/Examples.hs view
@@ -10,9 +10,6 @@ import Data.Monoid (Dual (..), Endo (..), Last) import Data.Tuple (swap) --- monoid-extras-import Data.Monoid.Action- -- mtl import Control.Monad.Reader (MonadReader (..)) import Control.Monad.State (MonadState (..), modify, runState)@@ -24,6 +21,7 @@ -- changeset import Control.Monad.Changeset.Class (MonadChangeset (..)) import Control.Monad.Trans.Changeset+import Data.Monoid.RightAction (RightAction (..))  -- tasty import Test.Tasty (TestTree, testGroup)@@ -45,8 +43,8 @@ -- | 'WriterT' is a special case of 'ChangesetT' when the current state is trivial. type TrivialActionWriterT w = ChangesetT () w -instance Action w () where-  act _ _ = ()+instance RightAction w () where+  actRight _ _ = ()  instance {-# OVERLAPPING #-} (Monoid w, Monad m) => MonadWriter w (TrivialActionWriterT w m) where   writer = ChangesetT . pure . pure . swap@@ -61,7 +59,7 @@ type LastWriteT s = ChangesetT s (Last s)  instance {-# OVERLAPPING #-} (Monad m) => MonadState s (LastWriteT s m) where-  state f = ChangesetT $ \s -> return $ first pure $ swap $ f s+  state f = ChangesetT $ \s -> pure $ first pure $ swap $ f s  -- * Another state monad @@ -72,7 +70,7 @@ type EndoStateT s = ChangesetT s (Dual (Endo s))  instance {-# OVERLAPPING #-} (Monad m) => MonadState s (EndoStateT s m) where-  state f = ChangesetT $ \s -> return (Dual $ Endo $ snd <$> f, fst $ f s)+  state f = ChangesetT $ \s -> pure (Dual $ Endo $ snd <$> f, fst $ f s)  type M = Changes (ListChange Int) @@ -91,7 +89,7 @@   n <- get   put 2   put 3-  return n+  pure n  tests :: TestTree tests =
src/Control/Monad/Changeset/Class.hs view
@@ -14,37 +14,41 @@  {- | Monads containing changeset state. -This usually implies that the 'Control.Monad.Trans.Changeset.ChangesetT' monad transformer is part of the monad transformer stack of @m.@+This usually implies that the 'Control.Monad.Trans.Changeset.ChangesetT' monad transformer is part of the monad transformer stack of @m@. See its documentation for details.  Two laws for these methods boil down to the requirement that 'change' and 'current' are special cases of 'changeset':  @-  change w = changeset $ const ((), w)-  current = changeset (, mempty)+  'change' w = 'changeset' $ 'const' ((), w)+  'current' = 'changeset' (, 'mempty') @ -The central law ensures that future  states are affected by the past changes through right action:+The central law ensures that future states are affected by the past changes through right action:  @-forall MonadChangeset s w m-       f :: s -> (a, w)-       g :: a -> s -> (b, w) .-changeset f >>= (changeset . g)+f :: s -> (a, w)+g :: a -> s -> (b, w)++'changeset' f >>= ('changeset' . g)   =-changeset $ \s -> let (a, w) = f s in g a $ s `actRight` w+'changeset' $ \s -> let (a, w) = f s in g a $ s \`'Data.Monoid.RightAction.actRight'\` w @  This law has several easier to grasp corollaries:+ @-change w >> current = do-  s <- current-  change w-  return $ s 'actRight' w+--  Applying a change and then observing the state is the same as observing the state, updating it, and applying the change.+'change' w >> 'current' = do+  s <- 'current'+  'change' w+  'return' $ s \`'Data.Monoid.RightAction.actRight'\` w -change w1 >> change w2 = change (w1 <> w2)+-- Changes are combined through the semigroup product+'change' w1 >> 'change' w2 = 'change' (w1 <> w2) -current s1 >> current s2 = current s2+-- current has no effect other than returning the current state+'current' >> 'current' = 'current' @ -} class (Monad m, Monoid w, RightAction w s) => MonadChangeset s w m | m -> s, m -> w where@@ -54,17 +58,19 @@     (s -> (a, w)) ->     m a -  -- | Apply a change to the state.-  ---  --   The 'Action' instance is used to mutate the state.-  ---  --   This is a special case of 'changeset' where the current state is disregarded.+  {- | Apply a change to the state.++  The 'RightAction' instance is used to mutate the state.++  This is a special case of 'changeset' where the current state is disregarded.+  -}   change :: w -> m ()   change w = changeset $ const ((), w) -  -- | Observe the current state.-  ---  --   This is a special case of 'changeset' where the state is not changed.+  {- | Observe the current state.++  This is a special case of 'changeset' where the state is not changed.+  -}   current :: m s   current = changeset (,mempty) @@ -75,8 +81,17 @@  {- | Calculate the difference from the current state to the explicitly given state, and return it. -With a lawful @'RightTorsor' w s@ instance, it can be expected that after then applying the difference, the state is the explicitly given one:-After @diff s >>= change@, the current state is @s@.+Also see 'update'. -} diff :: (RightTorsor w s, MonadChangeset s w m) => s -> m w diff s = (`differenceRight` s) <$> current++{- | Update to a specific state.++Uses 'diff' to calculate the missing change to the intended state.+This is only possible if the change is also a /torsor/, that is, there is a way to calculate a change as a difference between states.++With a lawful @'RightTorsor' w s@ instance, it can be expected that after applying @'update' s@, the state is @s@.+-}+update :: (MonadChangeset s w m, RightTorsor w s) => s -> m ()+update s = diff s >>= change
src/Control/Monad/Trans/Changeset.hs view
@@ -31,19 +31,27 @@ @  Changes for such a type (or rather, for the monoid @'Changes' ChangeAddress@) can be inspected.+This then opens several possibilities: +* We can display and edit the planned changes+* We can perform side effects dependent on the particular change that can be made,+  for example redrawing a corresponding UI element or performing a DB query+* We can calculate changes as a diff between states and merge diffs+ 'ChangesetT' is a very general state monad transformer. It has all the standard state monads from @transformers@ as special cases: -+--------------------------+---------------+-------------+---------------------------------------------+-| Transformer special case | State type    | Monoid type | Intuition                                   |-+==========================+===============+=============+=============================================+-| @'WriterT' w@            | '()'          | @w@         | No possibility to observe the current state |-+--------------------------+---------------+-------------+---------------------------------------------+-| @'AccumT' w@             | @'Regular' w@ | @w@         | The state is the same type as the changes   |-+--------------------------+---------------+-------------+---------------------------------------------+-| @'StateT' s@             | @s@           | @First s@   | The change overwrites all previous changes  |-+--------------------------+---------------+-------------+---------------------------------------------+++--------------------------+---------------+-------------------------+---------------------------------------------++| Transformer special case | State type    | Change type             | Intuition                                   |++==========================+===============+=========================+=============================================++| @'WriterT' w@            | '()'          | @w@                     | No possibility to observe the current state |++--------------------------+---------------+-------------------------+---------------------------------------------++| @'AccumT' w@             | @'Regular' w@ | @w@                     | The state is the same type as the changes   |++--------------------------+---------------+-------------------------+---------------------------------------------++| @'StateT' s@             | @s@           | @'Data.Monoid.Last' s@  | The change overwrites all previous changes  |++--------------------------+---------------+-------------------------+---------------------------------------------++| @'ReaderT' r@            | @r@           | '()'                    | The state cannot be changed                 |++--------------------------+---------------+-------------------------+---------------------------------------------+  The @changeset@ ecosystem has support for standard @containers@ and optics from @lens@ by providing the packages [@changeset-containers@](https://hackage.haskell.org/package/changeset-containers) and [@changeset-lens@](https://hackage.haskell.org/package/changeset-lens).@@ -112,11 +120,11 @@ and these can depend on the current state.  The type @w@ encodes /changes/ (or updates, edits, commits, diffs, patches ...) to the state @s.@-This relation is captured by the 'RightAction' type class from @monoid-extras.@-It contains a method, @'actRight' :: w -> s -> s@,+This relation is captured by the 'RightAction' type class from "Data.Monoid.RightAction".+It contains a method, @'actRight' :: s -> w -> s@, which implements the semantics of @w@ as the type of updates to @s.@ -The standard example is that of a big record where we only want to change a small portion:+A standard example is that of a big record where we only want to change a small portion:  @ data User = User@@ -276,7 +284,7 @@     (w1, a) <- ma s     let !s' = actRight s w1     (w2, b) <- getChangesetT (f a) s'-    return (w1 <> w2, b)+    pure (w1 <> w2, b)  instance (Alternative m, Monoid w, RightAction w s, Monad m) => Alternative (ChangesetT s w m) where   empty = liftF empty@@ -420,6 +428,9 @@ newtype SetTo a = SetTo a   deriving stock (Eq, Show, Read, Ord, Generic, Functor, Foldable, Traversable) +instance Semigroup (SetTo a) where+  _ <> a = a+ instance RightAction (SetTo a) a where   actRight _ (SetTo a) = a @@ -465,6 +476,10 @@ instance RightAction (MaybeChange a) (Maybe a) where   actRight aMaybe MaybeChange {getMaybeChange} = actRight aMaybe getMaybeChange +{- | __Warning:__ This instance does not satisfy the 'RightTorsor' law @differenceRight s (s \`actRight\` w) = w@.+When @w@ sets the value to the same state (e.g. @'setJust' x@ applied to @'Just' x@),+the round-trip produces 'mempty' instead of the original @w@.+-} instance (Eq a) => RightTorsor (MaybeChange a) (Maybe a) where   differenceRight Nothing Nothing = mempty   differenceRight (Just aOrig) (Just aChanged) = if aOrig == aChanged then mempty else setJust aChanged
src/Data/Monoid/RightAction.hs view
@@ -37,8 +37,6 @@  instance RightAction () s -instance RightAction m ()- instance RightAction Void s  instance (RightAction w1 s1, RightAction w2 s2) => RightAction (w1, w2) (s1, s2) where@@ -121,16 +119,24 @@ instance RightTorsor () s where   differenceRight _ _ = () --- | When the new state is equal to the original, produce the empty change, otherwise just 'set' to the new state.+{- | When the new state is equal to the original, produce the empty change, otherwise just 'set' to the new state.++__Warning:__ This instance does not satisfy the 'RightTorsor' law @differenceRight s (s \`actRight\` w) = w@.+When @w@ sets the value to the same state (e.g. @'Last' ('Just' x)@ applied to @x@),+the round-trip produces 'mempty' instead of the original @w@.+-} instance (Eq s) => RightTorsor (Last s) s where   differenceRight sOrig sActed = Last $ if sOrig == sActed then Nothing else Just sActed --- | Calculate the diff per position of the container.-instance (Semigroup w, RightTorsor w s, Zip f) => RightTorsor (f w) (f s) where+{- | Calculate the diff per position of the container.++This instance is marked as @{\-# OVERLAPPABLE #-\}@ so it is possible to define more specific instances for particular container types.+-}+instance {-# OVERLAPPABLE #-} (Semigroup w, RightTorsor w s, Zip f) => RightTorsor (f w) (f s) where   differenceRight = zipWith differenceRight -instance {-# OVERLAPPING #-} (Num a) => RightTorsor (Sum a) (Sum a) where+instance (Num a) => RightTorsor (Sum a) (Sum a) where   differenceRight = flip (-) -instance {-# OVERLAPPING #-} (Fractional a) => RightTorsor (Product a) (Product a) where+instance (Fractional a) => RightTorsor (Product a) (Product a) where   differenceRight (Product aOld) (Product aNew) = Product $ aNew / aOld
src/Data/Monoid/RightAction/Coproduct.hs view
@@ -52,5 +52,11 @@ instance (Eq m, Eq n, Semigroup m, Semigroup n) => Eq (m :+: n) where   mns1 == mns2 = normaliseCoproduct mns1 == normaliseCoproduct mns2 +-- | Coproducts are shown after normalising+instance (Show m, Show n, Semigroup m, Semigroup n) => Show (m :+: n) where+  showsPrec d mns =+    showParen (d > 10) $+      showString "Coproduct " . showsPrec 11 (normaliseCoproduct mns)+ instance (RightAction m s, RightAction n s) => RightAction (m :+: n) s where   actRight s mns = foldl' (flip $ either (flip actRight) (flip actRight)) s (getCoproduct mns)
test/Main.hs view
@@ -1,10 +1,14 @@+{-# 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 (..)) @@ -17,6 +21,16 @@ -- 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 @@ -24,10 +38,11 @@ import Control.Monad.Changeset.Class import Control.Monad.Trans.Changeset import Data.Monoid.RightAction (RightAction (..), RightTorsor (..), rEndo, set)-import Data.Monoid.RightAction.Coproduct (inL, (:+:))+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 =@@ -46,7 +61,7 @@                       let action = flip evalChangeset 0 $ do                             n <- current                             changeSingle Increment-                            return n+                            pure n                        in action @?= (0 :: Int)                   ]               , testGroup@@ -70,11 +85,36 @@               ]           ]       , testGroup-          "Changes"-          [ testCase "is lawful monoid action" $ do+          "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@@ -82,10 +122,64 @@                     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"@@ -122,6 +216,23 @@                       ]                   )                 @?= 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"@@ -155,8 +266,27 @@                   [ 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)+              , 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+                  )               ]           ]       ]@@ -224,6 +354,15 @@ 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@@ -231,3 +370,205 @@   , 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