GithubHelp home page GithubHelp logo

blamario / monoid-subclasses Goto Github PK

View Code? Open in Web Editor NEW
33.0 33.0 9.0 364 KB

Subclasses of Monoid with a solid theoretical foundation and practical purposes

License: BSD 3-Clause "New" or "Revised" License

Haskell 100.00%

monoid-subclasses's People

Contributors

blamario avatar bodigrim avatar edwardbetts avatar endgame avatar ericson2314 avatar jonathanknowles avatar mgiles avatar phadej avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

monoid-subclasses's Issues

CancellativeMonoid vs Group

The README states that

Every group (i.e., every Monoid a with the operation inverse :: a -> a) is a CancellativeMonoid where a </> b = Just (a <> inverse b) but not every CancellativeMonoid is a group.

However, couldn't every CancellativeMonoid be a Group, with inverse a = mempty </> a? Meanwhile, not every group is a CancellativeMonoid if ReductiveMonoids (and hence CancellativeMonoids) are required to be Abelian.

Compile failure with pre-AMP `base`

I've already revised the affected releases:

Configuring component lib from monoid-subclasses-0.4.3.1...
Preprocessing library monoid-subclasses-0.4.3.1...
[1 of 9] Compiling Data.Monoid.Null ( Data/Monoid/Null.hs, /tmp/matrix-worker/1483888295/dist-newstyle/build/x86_64-linux/ghc-7.8.4/monoid-subclasses-0.4.3.1/build/Data/Monoid/Null.o )
[2 of 9] Compiling Data.Monoid.Factorial ( Data/Monoid/Factorial.hs, /tmp/matrix-worker/1483888295/dist-newstyle/build/x86_64-linux/ghc-7.8.4/monoid-subclasses-0.4.3.1/build/Data/Monoid/Factorial.o )
[3 of 9] Compiling Data.Monoid.Cancellative ( Data/Monoid/Cancellative.hs, /tmp/matrix-worker/1483888295/dist-newstyle/build/x86_64-linux/ghc-7.8.4/monoid-subclasses-0.4.3.1/build/Data/Monoid/Cancellative.o )

Data/Monoid/Cancellative.hs:341:41:
    Not in scope: ‘<$>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:341:57:
    Not in scope: ‘<*>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:341:73:
    Not in scope: ‘<*>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:349:49:
    Not in scope: ‘<$>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:349:71:
    Not in scope: ‘<*>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:349:93:
    Not in scope: ‘<*>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:354:49:
    Not in scope: ‘<$>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:354:71:
    Not in scope: ‘<*>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:354:93:
    Not in scope: ‘<*>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:376:50:
    Not in scope: ‘<$>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:376:66:
    Not in scope: ‘<*>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:376:82:
    Not in scope: ‘<*>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:376:98:
    Not in scope: ‘<*>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:387:13:
    Not in scope: ‘<$>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:387:35:
    Not in scope: ‘<*>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:387:57:
    Not in scope: ‘<*>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:387:79:
    Not in scope: ‘<*>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:394:13:
    Not in scope: ‘<$>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:394:35:
    Not in scope: ‘<*>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:394:57:
    Not in scope: ‘<*>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Data/Monoid/Cancellative.hs:394:79:
    Not in scope: ‘<*>’
    Perhaps you meant one of these:
      ‘<>’ (imported from Data.Monoid), ‘</>’ (line 78)

Support text-2.0

cabal build --constraint 'text >= 2.0' --allow-newer='monoid-subclasses:text' fails with

src/Data/Monoid/GCD.hs:39:36: error:
    Module ‘Data.Text.Unsafe’ does not export ‘lengthWord16’
   |
39 | import           Data.Text.Unsafe (lengthWord16, reverseIter)
   |

Test failure

Hi! I'm not much aware of internals of this library, but I've got this test error during my nix build (probably some dependency is using this library):

  Textual.map:                  FAIL (0.10s)
    *** Failed! Exception: 'Prelude.Enum.Char.succ: bad argument' (after 85 tests):
    "\GS>\584242!\790073#\1019665byz\151329\524869E~F\NAK\432538F]G\ACK\1114111S\212292PuA?g\f\t~\548909Ve\111469\804037A\SYN(\ESChTF\578048Ffm\86897.2\CAN\527674j7:\1036814%\54002\144114J`-Az\b-\874895\132836W\ENQ\1089792m\DEL"
    Use --quickcheck-replay=142125 to reproduce.

1 out of 72 tests failed (26.58s)
Test suite Main: FAIL
Test suite logged to: dist/test/monoid-subclasses-0.4.6.1-Main.log

TextualMonid vs FactorialMonoid

Everything else in this package seems to provide real value, and be built on good theoretical foundations. What does TextualMonoid add? It seems to dupe a lot of methods from FactorialMonoid even though that class is also required...

build failure on ghc 8.0

4 of 9] Compiling Data.Monoid.Textual ( Data/Monoid/Textual.hs, dist/build/Data/Monoid/Textual.o )

Data/Monoid/Textual.hs:354:15: error:
The INLINE pragma for ‘split’ lacks an accompanying binding
(The INLINE pragma must be given where ‘split’ is declared)

Data/Monoid/Textual.hs:537:15: error:
The INLINE pragma for ‘fromText’ lacks an accompanying binding
(The INLINE pragma must be given where ‘fromText’ is declared)

Data/Monoid/Textual.hs:539:15: error:
The INLINE pragma for ‘mapAccumL’ lacks an accompanying binding
(The INLINE pragma must be given where ‘mapAccumL’ is declared)

Data/Monoid/Textual.hs:540:15: error:
The INLINE pragma for ‘mapAccumR’ lacks an accompanying binding
(The INLINE pragma must be given where ‘mapAccumR’ is declared)

Data/Monoid/Textual.hs:549:15: error:
The INLINE pragma for ‘split’ lacks an accompanying binding
(The INLINE pragma must be given where ‘split’ is declared)

Data/Monoid/Textual.hs:611:15: error:
The INLINE pragma for ‘fromText’ lacks an accompanying binding
(The INLINE pragma must be given where ‘fromText’ is declared)

Data/Monoid/Textual.hs:623:15: error:
The INLINE pragma for ‘split’ lacks an accompanying binding
(The INLINE pragma must be given where ‘split’ is declared)

Build failure with ghc 8.4

Building library for monoid-subclasses-0.4.4..

[5 of 9] Compiling Data.Monoid.Instances.Stateful ( Data/Monoid/Instances/Stateful.hs, dist/build/Data/Monoid/Instances/Stateful.o )

Data/Monoid/Instances/Stateful.hs:58:10: error:
    • Could not deduce (Semigroup (Stateful a b))
        arising from the superclasses of an instance declaration
      from the context: (Monoid a, Monoid b)
        bound by the instance declaration
        at Data/Monoid/Instances/Stateful.hs:58:10-54
    • In the instance declaration for ‘Monoid (Stateful a b)’
   |
58 | instance (Monoid a, Monoid b) => Monoid (Stateful a b) where
   |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Suspected unlawful `Monus` instance for `Maybe`.

Hi @blamario

I think I've discovered a counterexample to the Monus laws for Maybe.

I tested against the latest version of monoid-subclasses in this repository: 9255072

Counterexample

First some imports:

> import Data.Monoid (Sum (Sum))
> import Data.Monoid.GCD (OverlappingGCDMonoid (..))
> import Data.Monoid.Monus (Monus (..))
> import Numeric.Natural (Natural)

Then define the counterexample arguments:

> a = Just (Sum (0 :: Natural))
> b = Just (Sum (0 :: Natural))

The laws stated in the documentation are as follows (source):

  • (<\>) = flip stripPrefixOverlap
  • (<\>) = flip stripSuffixOverlap

When we test these laws with the example arguments, it seems that the laws are not satisfied:

> (<\>) a b == flip stripPrefixOverlap a b
False
> (<\>) a b == flip stripSuffixOverlap a b
False

If we evaluate just the LHS, we get:

> (<\>) a b
Just (Sum {getSum = 0})

If we evaluate just the RHS, we get:

> flip stripPrefixOverlap a b
Nothing
> flip stripSuffixOverlap a b
Nothing

Perhaps I've interpreted the laws incorrectly?

Many thanks!

Jonathan

Remove conditional laws

I personally am against "conditional laws", which is laws of the form "if T is also an instance of C, then ...", as they are useless for types that don't implement C, so you can't really build any intuition about the functions in question in the general case. They also give rise to the possibility of a new instance invalidating a previously valid different instance of a seemingly unrelated (not sub or super) typeclass.

The main one I am aware of is:

If a GCDMonoid happens to also be a CancellativeMonoid, it should additionally satisfy the following laws:

But any others I would also be in favor of removing. Perhaps the above could be replaced with a class that is a subclass of GCD and Cancellative and asserts the above.

git tree lacks some files

Tried to chase testsuite

  Textual.foldl:                FAIL (0.02s)
    *** Failed! (after 23 tests): 
    Exception:
      Data/Monoid/Instances/ByteString/UTF8.hs:376:3-8: Assertion failed
    "[\[237]g\[159]\[253]x]\[139]\28\[191,134]|"
    Use --quickcheck-replay '22 TFGenR 0000000807931105000000000007A120000000000000DF8400000002540BE400 0 36028792723996672 55 0' to reproduce.

but it seems that git tree lacks some files:

Configuring monoid-subclasses-0.4.1.1...
Building monoid-subclasses-0.4.1.1...
Preprocessing library monoid-subclasses-0.4.1.1...
Setup: can't find source for Data/Monoid/Instances/Markup in .,
dist/build/autogen

Add `Group` class

Group would be a subclass of LeftCancellative and RightCancellative but not Cancellative itself since Group is not required to be commutative. Abelian would be a subclass of Cancellative and Group. LeftGroup and RightGroup could also work as superclasses.

One thing that this brings up is that maybe a non-commutative variant of the neutral classes is worthwhile, as any non-commutative group is still going to be a non-commutative cancellative.

`LCMMonoid` type class

Hi @blamario

I'm writing a set of libraries to work with monoidal structures (including total-monoidal-maps, which is not yet released).

I'd love to be able provide a generalised lcm method for commutative monoids that admit an LCM, similar to the existing gcd method of GCDMonoid.

Would you be open to me creating a PR that adds an LCMMonoid type class to monoid-subclasses? (I actually already have a unpolished prototype: I'd be happy to turn it into a more polished PR with documentation, instances and tests, provided you think it's a good idea?)

Proposal

Assuming a minimal set of superclass dependencies, we might define LCMMonoid as:

class (Monoid m, Reductive m) => LCMMonoid m where
    lcm :: m -> m -> m

Where lcm would satisfy the laws we might expect from LCM:

-- Identity
lcm a mempty == a
lcm mempty a == a

-- Idempotence
lcm a a == a

-- Commutativity
lcm a b == lcm b a

-- Associativity
lcm (lcm a b) c == lcm a (lcm b c)

-- Distributivity
lcm (a <> b) (a <> c) == a <> lcm b c
lcm (a <> c) (b <> c) == lcm a b <> c

-- Reductivity
isJust (lcm a b </> a)
isJust (lcm a b </> b)

-- Uniqueness
all isJust [c </> a, c </> b, lcm a b </> c] ==> lcm a b == c

Justification

Given certain typeclass constraints, it is already possible to compute an LCM. For example:

  • With GCDMonoid and Monus:
    lcm a b = (a <\> b) <> gcd a b <> (b <\> a)
  • With Commutative and OverlappingGCDMonoid:
    lcm a b = stripSuffixOverlap b a <> overlap a b <> stripPrefixOverlap a b

However, for some types, we can provide a more direct (and potentially more efficient) implementation. For example:

instance LCMMonoid (Product Natural) where
    lcm (Product a) (Product b) = Product (Prelude.lcm a b)

instance LCMMonoid (Sum Natural) where
    lcm (Sum a) (Sum b) = Sum (Prelude.max a b)

instance Ord a => LCMMonoid (Set a) where
    lcm = Set.union
    
instance Ord a => LCMMonoid (IntSet a) where
    lcm = IntSet.union

Alternative ideas

Provide LCMMonoid in a separate library

If you'd prefer not to include LCMMonoid within monoid-subclasses, I would be happy to publish this class in a separate library that extends monoid-subclasses.

Go further, and provide a richer tapestry of superclasses

Similar to the GCDMonoid type class (which has three superclasses), I can imagine an analogous set of superclasses for LCMMonoid, including:

class (Monoid m, LeftReductive m) => LeftLCMMonoid m where
    leftLCM :: m -> m -> m

class (Monoid m, RightReductive m) => RightLCMMonoid m where
    rightLCM :: m -> m -> m

These might have analogous laws to LCMMonoid, but defined in terms of LeftReductive and RightReductive instead of Reductive.

And similar to OverlappingGCDMonoid, we might imagine an OverlappingLCMMonoid class:

class (Monoid m, LeftReductive m, RightReductive m) => OverlappingLCMMonoid where
    overlapLCM :: m -> m -> m

    -- Just for illustration, could be provided by a newtype and separate instance:
    default overlapLCM :: OverlappingGCDMonoid m => m -> m -> m
    overlapLCM a b =
        stripSuffixOverlap b a <> overlap a b <> stripPrefixOverlap a b

However, after reading through as much research literature as I could find about left- and right-LCMs, it's still not clear to me how useful non-Abelian LCM operations are in practice, especially for data structures we encounter in the "real world". (In particular, for string-like and list-like types, not all pairs of values admit a left-LCM or right-LCM, so attempting to extend this hypothetical hierarchy to such types might require leftLCM and rightLCM to have type m -> m -> Maybe m, which would break symmetry with the GCDMonoid family.) Though perhaps I've missed something?

Anyway, I'd love to hear your thoughts on the proposal to simply add a LCMMonoid class as a subclass of Monoid and Reductive. I'd be happy to create a PR if you like.

Thanks for reading!

monoid-subclasses-0.4.0.3 test suite failure

Citing from http://hydra.nixos.org/build/19277425/nixlog/2/raw:

Textual.foldl':               FAIL (0.36s)
  *** Failed! Falsifiable (after 73 tests): 
  "?\[137].\[138,158]<xT\[142]!x5ª\14^_\[162,173]-\[156]\[223]<s\[219]\[227]\[254]\[240,182]`\[254]\[233]\[243]\[208]\[249]\18\5\[248]\[230]\[244]\12\15Ld<\[240,147,154,162]H"
  Use --quickcheck-replay '72 TFGenR 2ABAADACF782BEC9F280B33CC904F800B137F1A77391A8DDF195E5BA950B6885 0 8796093022207 43 0' to reproduce.

Test failure with GHC 8.2

> /tmp/stackage-build14/monoid-subclasses-0.4.3.2$ ghc -clear-package-db -global-package-db -package-db=/var/stackage/work/builds/nightly/pkgdb Setup
[1 of 1] Compiling Main             ( Setup.hs, Setup.o )
Linking Setup ...
> /tmp/stackage-build14/monoid-subclasses-0.4.3.2$ ./Setup configure --enable-tests --package-db=clear --package-db=global --package-db=/var/stackage/work/builds/nightly/pkgdb --libdir=/var/stackage/work/builds/nightly/lib --bindir=/var/stackage/work/builds/nightly/bin --datadir=/var/stackage/work/builds/nightly/share --libexecdir=/var/stackage/work/builds/nightly/libexec --sysconfdir=/var/stackage/work/builds/nightly/etc --docdir=/var/stackage/work/builds/nightly/doc/monoid-subclasses-0.4.3.2 --htmldir=/var/stackage/work/builds/nightly/doc/monoid-subclasses-0.4.3.2 --haddockdir=/var/stackage/work/builds/nightly/doc/monoid-subclasses-0.4.3.2 --flags=
Configuring monoid-subclasses-0.4.3.2...
> /tmp/stackage-build14/monoid-subclasses-0.4.3.2$ ghc -clear-package-db -global-package-db -package-db=/var/stackage/work/builds/nightly/pkgdb Setup
> /tmp/stackage-build14/monoid-subclasses-0.4.3.2$ ./Setup build
Preprocessing library for monoid-subclasses-0.4.3.2..
Building library for monoid-subclasses-0.4.3.2..
[1 of 9] Compiling Data.Monoid.Cancellative ( Data/Monoid/Cancellative.hs, dist/build/Data/Monoid/Cancellative.o )
[2 of 9] Compiling Data.Monoid.Null ( Data/Monoid/Null.hs, dist/build/Data/Monoid/Null.o )
[3 of 9] Compiling Data.Monoid.Factorial ( Data/Monoid/Factorial.hs, dist/build/Data/Monoid/Factorial.o )
[4 of 9] Compiling Data.Monoid.Textual ( Data/Monoid/Textual.hs, dist/build/Data/Monoid/Textual.o )

Data/Monoid/Textual.hs:545:1: warning: [-Worphans]
    Orphan instance: instance IsString (Vector.Vector Char)
    To avoid this
        move the instance declaration to the module of the class or of the type, or
        wrap the type with a newtype and declare the instance on the new type.
    |
545 | instance IsString (Vector.Vector Char) where
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...
[5 of 9] Compiling Data.Monoid.Instances.Stateful ( Data/Monoid/Instances/Stateful.hs, dist/build/Data/Monoid/Instances/Stateful.o )
[6 of 9] Compiling Data.Monoid.Instances.Positioned ( Data/Monoid/Instances/Positioned.hs, dist/build/Data/Monoid/Instances/Positioned.o )
[7 of 9] Compiling Data.Monoid.Instances.Measured ( Data/Monoid/Instances/Measured.hs, dist/build/Data/Monoid/Instances/Measured.o )
[8 of 9] Compiling Data.Monoid.Instances.Concat ( Data/Monoid/Instances/Concat.hs, dist/build/Data/Monoid/Instances/Concat.o )
[9 of 9] Compiling Data.Monoid.Instances.ByteString.UTF8 ( Data/Monoid/Instances/ByteString/UTF8.hs, dist/build/Data/Monoid/Instances/ByteString/UTF8.o )
Preprocessing test suite 'Main' for monoid-subclasses-0.4.3.2..
Building test suite 'Main' for monoid-subclasses-0.4.3.2..

<no location info>: warning: [-Wmissing-home-modules]
    These modules are needed for compilation but not listed in your .cabal file's other-modules: Data.Monoid.Cancellative
                                                                                                 Data.Monoid.Factorial
                                                                                                 Data.Monoid.Instances.ByteString.UTF8
                                                                                                 Data.Monoid.Instances.Concat
                                                                                                 Data.Monoid.Instances.Measured
                                                                                                 Data.Monoid.Instances.Positioned
                                                                                                 Data.Monoid.Instances.Stateful
                                                                                                 Data.Monoid.Null
                                                                                                 Data.Monoid.Textual
[ 1 of 10] Compiling Data.Monoid.Cancellative ( Data/Monoid/Cancellative.hs, dist/build/Main/Main-tmp/Data/Monoid/Cancellative.o )
[ 2 of 10] Compiling Data.Monoid.Null ( Data/Monoid/Null.hs, dist/build/Main/Main-tmp/Data/Monoid/Null.o )
[ 3 of 10] Compiling Data.Monoid.Factorial ( Data/Monoid/Factorial.hs, dist/build/Main/Main-tmp/Data/Monoid/Factorial.o )
[ 4 of 10] Compiling Data.Monoid.Textual ( Data/Monoid/Textual.hs, dist/build/Main/Main-tmp/Data/Monoid/Textual.o )
[ 5 of 10] Compiling Data.Monoid.Instances.Stateful ( Data/Monoid/Instances/Stateful.hs, dist/build/Main/Main-tmp/Data/Monoid/Instances/Stateful.o )
[ 6 of 10] Compiling Data.Monoid.Instances.Positioned ( Data/Monoid/Instances/Positioned.hs, dist/build/Main/Main-tmp/Data/Monoid/Instances/Positioned.o )
[ 7 of 10] Compiling Data.Monoid.Instances.Measured ( Data/Monoid/Instances/Measured.hs, dist/build/Main/Main-tmp/Data/Monoid/Instances/Measured.o )
[ 8 of 10] Compiling Data.Monoid.Instances.Concat ( Data/Monoid/Instances/Concat.hs, dist/build/Main/Main-tmp/Data/Monoid/Instances/Concat.o )
[ 9 of 10] Compiling Data.Monoid.Instances.ByteString.UTF8 ( Data/Monoid/Instances/ByteString/UTF8.hs, dist/build/Main/Main-tmp/Data/Monoid/Instances/ByteString/UTF8.o )
[10 of 10] Compiling Main             ( Test/TestMonoidSubclasses.hs, dist/build/Main/Main-tmp/Main.o )

<no location info>: warning: [-Wmissing-home-modules]
    These modules are needed for compilation but not listed in your .cabal file's other-modules: Data.Monoid.Cancellative
                                                                                                 Data.Monoid.Factorial
                                                                                                 Data.Monoid.Instances.ByteString.UTF8
                                                                                                 Data.Monoid.Instances.Concat
                                                                                                 Data.Monoid.Instances.Measured
                                                                                                 Data.Monoid.Instances.Positioned
                                                                                                 Data.Monoid.Instances.Stateful
                                                                                                 Data.Monoid.Null
                                                                                                 Data.Monoid.Textual
Linking dist/build/Main/Main ...
> /tmp/stackage-build14/monoid-subclasses-0.4.3.2$ dist/build/Main/Main
MonoidSubclasses
  CommutativeMonoid:            OK (0.02s)
    +++ OK, passed 100 tests.
  MonoidNull:                   OK (1.20s)
    +++ OK, passed 100 tests.
  PositiveMonoid:               OK (0.37s)
    +++ OK, passed 100 tests.
  mconcat . factors == id:      OK (1.97s)
    +++ OK, passed 100 tests.
  all factors . factors:        OK (1.58s)
    +++ OK, passed 100 tests.
  splitPrimePrefix:             OK (1.70s)
    +++ OK, passed 100 tests.
  splitPrimeSuffix:             OK (3.29s)
    +++ OK, passed 100 tests.
  primePrefix:                  OK (0.71s)
    +++ OK, passed 100 tests.
  primeSuffix:                  OK (0.82s)
    +++ OK, passed 100 tests.
  inits:                        OK (0.59s)
    +++ OK, passed 100 tests.
  tails:                        OK (0.38s)
    +++ OK, passed 100 tests.
  foldl:                        OK (1.56s)
    +++ OK, passed 100 tests.
  foldl':                       OK (1.64s)
    +++ OK, passed 100 tests.
  foldr:                        OK (1.48s)
    +++ OK, passed 100 tests.
  length:                       OK (0.85s)
    +++ OK, passed 100 tests.
  span:                         OK (2.12s)
    +++ OK, passed 100 tests.
  spanMaybe:                    OK (1.56s)
    +++ OK, passed 100 tests.
  split:                        OK (11.49s)
    +++ OK, passed 100 tests.
  splitAt:                      OK (2.06s)
    +++ OK, passed 100 tests.
  reverse:                      OK (2.13s)
    +++ OK, passed 100 tests.
  stable:                       OK (0.85s)
    +++ OK, passed 100 tests.
  fromText:                     OK (0.11s)
    +++ OK, passed 100 tests.
  singleton:                    OK (0.01s)
    +++ OK, passed 100 tests.
  Textual.splitCharacterPrefix: FAIL (0.03s)
    *** Failed! Falsifiable (after 31 tests): 
    "\674739\101949$\265771zm1|\644514\a\NAK\1022883\ENQ\DEL\SIV=U^z\324855\\K\1083335\STX"
    Use --quickcheck-replay=826082 to reproduce.
  Textual.characterPrefix:      OK (0.06s)
    +++ OK, passed 100 tests.
  Textual factors:              OK (0.10s)
    +++ OK, passed 100 tests.
  Textual.unfoldr:              OK (0.15s)
    +++ OK, passed 100 tests.
  factors . fromString:         FAIL
    *** Failed! Falsifiable (after 13 tests): 
    "f\1080552r\FS"
    Use --quickcheck-replay=518447 to reproduce.
  Textual.map:                  FAIL (0.01s)
    *** Failed! Falsifiable (after 15 tests): 
    "W=\1084715\869485"
    Use --quickcheck-replay=799373 to reproduce.
  Textual.concatMap:            FAIL (0.05s)
    *** Failed! Falsifiable (after 31 tests): 
    "\STXJ6\1070498\434832!Yi\bubg"
    Use --quickcheck-replay=349840 to reproduce.
  Textual.any:                  OK (0.07s)
    +++ OK, passed 100 tests.
  Textual.all:                  OK (0.06s)
    +++ OK, passed 100 tests.
  Textual.foldl:                OK (0.30s)
    +++ OK, passed 100 tests.
  Textual.foldr:                FAIL
    *** Failed! (after 12 tests): 
    Exception:
      Prelude.undefined
      CallStack (from HasCallStack):
        error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err
        undefined, called at Test/TestMonoidSubclasses.hs:541:35 in main:Main
    "+K\\|C\RS\RSr\102662\1056399"
    Use --quickcheck-replay=294082 to reproduce.
  Textual.foldl':               OK (0.30s)
    +++ OK, passed 100 tests.
  Textual.scanl:                FAIL (0.02s)
    *** Failed! Falsifiable (after 19 tests): 
    "QE\412173i\DC1^\1059018\291842[\896957i\NUL\RSM"
    Use --quickcheck-replay=761762 to reproduce.
  Textual.scanr:                FAIL
    *** Failed! Falsifiable (after 5 tests): 
    "\FS\1059735"
    Use --quickcheck-replay=508482 to reproduce.
  Textual.scanl1:               FAIL
    *** Failed! Falsifiable (after 7 tests): 
    "7Q\1091238\318676,D"
    Use --quickcheck-replay=782289 to reproduce.
  Textual.scanr1:               FAIL
    *** Failed! Falsifiable (after 9 tests): 
    ")\RSQ\1108871\1066543V"
    Use --quickcheck-replay=356025 to reproduce.
  Textual.toString:             FAIL
    *** Failed! (after 11 tests): 
    Exception:
      Prelude.undefined
      CallStack (from HasCallStack):
        error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err
        undefined, called at Test/TestMonoidSubclasses.hs:595:38 in main:Main
    "F\703127\1087654\463388\307541b\575059:\RS"
    Use --quickcheck-replay=114826 to reproduce.
  Textual.mapAccumL:            FAIL
    *** Failed! Falsifiable (after 15 tests): 
    "*L\FS\825702\852301\13827\1106677\348970U\1052006\b>"
    Use --quickcheck-replay=257273 to reproduce.
  Textual.mapAccumR:            FAIL (0.01s)
    *** Failed! Falsifiable (after 18 tests): 
    "\82458\794518-\r\n\"j\DC3\381619:\1106219c\ESC#*\730954\SYN"
    Use --quickcheck-replay=542214 to reproduce.
  Textual.takeWhile:            FAIL (0.01s)
    *** Failed! (after 19 tests): 
    Exception:
      Prelude.undefined
      CallStack (from HasCallStack):
        error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err
        undefined, called at Test/TestMonoidSubclasses.hs:616:39 in main:Main
    "\1065643S\1055975\DLE"
    Use --quickcheck-replay=187325 to reproduce.
  Textual.dropWhile:            FAIL (0.06s)
    *** Failed! (after 41 tests): 
    Exception:
      Prelude.undefined
      CallStack (from HasCallStack):
        error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err
        undefined, called at Test/TestMonoidSubclasses.hs:623:39 in main:Main
    "\1077255Q__\133514\&3\aPZ<'\851187d\1026074q9XS1ml}aDv\ETB\STX"
    Use --quickcheck-replay=566090 to reproduce.
  Textual.span:                 OK (0.12s)
    +++ OK, passed 100 tests.
  Textual.break:                OK (0.12s)
    +++ OK, passed 100 tests.
  Textual.spanMaybe:            OK (0.16s)
    +++ OK, passed 100 tests.
  Textual.split:                OK (0.14s)
    +++ OK, passed 100 tests.
  Textual.find:                 OK (0.15s)
    +++ OK, passed 100 tests.
  Textual.foldl_:               OK (0.26s)
    +++ OK, passed 100 tests.
  Textual.foldr_:               FAIL
    *** Failed! Falsifiable (after 6 tests): 
    "\620525\1073615\&8}"
    Use --quickcheck-replay=682116 to reproduce.
  Textual.foldl_':              OK (0.26s)
    +++ OK, passed 100 tests.
  Textual.span_:                OK (0.14s)
    +++ OK, passed 100 tests.
  Textual.break_:               OK (0.10s)
    +++ OK, passed 100 tests.
  Textual.spanMaybe_:           OK (0.17s)
    +++ OK, passed 100 tests.
  Textual.spanMaybe_':          OK (0.17s)
    +++ OK, passed 100 tests.
  Textual.takeWhile_:           FAIL
    *** Failed! (after 5 tests): 
    Exception:
      Prelude.undefined
      CallStack (from HasCallStack):
        error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err
        undefined, called at Test/TestMonoidSubclasses.hs:676:40 in main:Main
    "\1085725"
    Use --quickcheck-replay=878239 to reproduce.
  Textual.dropWhile_:           OK (0.30s)
    +++ OK, passed 100 tests.
  stripPrefix:                  OK (1.05s)
    +++ OK, passed 100 tests.
  isPrefixOf:                   OK (1.00s)
    +++ OK, passed 100 tests.
  stripSuffix:                  OK (1.09s)
    +++ OK, passed 100 tests.
  isSuffixOf:                   OK (1.07s)
    +++ OK, passed 100 tests.
  </>:                          OK (0.01s)
    +++ OK, passed 100 tests.
  cancellative stripPrefix:     OK (0.06s)
    +++ OK, passed 100 tests.
  cancellative stripSuffix:     OK (0.05s)
    +++ OK, passed 100 tests.
  cancellative </>:             OK
    +++ OK, passed 100 tests.
  stripCommonPrefix 1:          OK (1.72s)
    +++ OK, passed 100 tests.
  stripCommonPrefix 2:          OK (1.48s)
    +++ OK, passed 100 tests.
  stripCommonSuffix 1:          OK (0.57s)
    +++ OK, passed 100 tests.
  stripCommonSuffix 2:          OK (0.54s)
    +++ OK, passed 100 tests.
  gcd:                          OK (0.02s)
    +++ OK, passed 100 tests.
  cancellative gcd:             OK
    +++ OK, passed 100 tests.

16 out of 72 tests failed (50.77s)

`instance Ord k => LeftReductiveMonoid (Map k a)` unlawful

stripPrefix is too "forgetful" and therefore does not obey maybe b (a <>) (stripPrefix a b) == b.

a = [(0, 0)]
b = [(0, 1)]
stripPrefix a b = Just []
maybe b (a <>) (stripPrefix a b) = [(0, 0)]
[(0, 0)] /= [(0, 1)]

Unfortunately I think the existing Monoid instances of Map and IntMap have trapped you here. Ideally something like instance (Ord k, LeftReductiveMonoid a) => LeftReductiveMonoid (Map k a) could have worked, to basically mirror the Maybe instance.

`Lexicographic` class would be useful for radix trees

Something along the lines of:

class (Ord m, LeftGCDMonoid m, MonoidNull m) => Lexicographic m where

To rule out cases where the ordering changes over the course of the comparison, we should have the following law:

prop :: Lexicographic m => m -> m -> m -> Bool
prop x y z = compare x y == compare (z <> x) (z <> y)

It seems like we may also need a law that any value in between two other values should share a equal or longer prefix with both of them than the two do with each other, to rule out cases like bar < foo < baz, as it's unclear whether or not that follows from the above law:

prop :: Lexicographic m => m -> m -> m -> Bool
prop x y z = (x <= y && y <= z)
          <= ( commonPrefix x z `isPrefixOf` commonPrefix x y
            && commonPrefix x z `isPrefixOf` commonPrefix y z
             )

However it's worth analyzing these laws further to make sure they are correct.

Given the above it would be possible to build an efficient radix-tree library built on top of Map that works for any arbitrary Lexicographic key.

Using the three classes separately has a variety of downsides:

You can't use Map.lookupLE/GT to search for shared prefixes, you have to exhaustively search the entire Map when inserting/editing.

You can't expose efficient minimum/lookupLT/etc. functions that run in logarithmic/key-length time and instead they all have to traverse the entire structure.

failure with GHC 8.2

I don't have a good reason to support builds with GHC 8.2, but maybe you could tweak some bounds so that an older version of monoid-subclasses gets selected on those systems.

    • Could not deduce (Semigroup b) arising from a use of ‘<>’
      from the context: (Factorial a, Monoid b, Monad m)
        bound by the type signature for:
                   mapM :: forall a b (m :: * -> *).
                           (Factorial a, Monoid b, Monad m) =>
                           (a -> m b) -> a -> m b
        at src/Data/Semigroup/Factorial.hs:422:1-66
      Possible fix:
        add (Semigroup b) to the context of
          the type signature for:
            mapM :: forall a b (m :: * -> *).
                    (Factorial a, Monoid b, Monad m) =>
                    (a -> m b) -> a -> m b
    • In the first argument of ‘Monad.liftM2’, namely ‘(<>)’
      In the first argument of ‘(.)’, namely ‘Monad.liftM2 (<>)’
      In the second argument of ‘(.)’, namely ‘Monad.liftM2 (<>) . f’
    |
423 | mapM f = ($ return mempty) . appEndo . Data.Semigroup.Factorial.foldMap (Endo . Monad.liftM2 (<>) . f)
    |                                                                                              ^^^^

Generalizing `TextualMonoid`

TextualMonoid has plenty of functionality that would be useful for non-Char containers, so I was thinking about ways to generalize it.

It seems like the core concept is that for a subset of FactorialMonoid's, the primes can fit a more restricted type than the original type, and can always be injected back into the original type with singleton. It seems like a decent abstraction is then to encode this directly:

class FactorialMonoid m => ContainerMonoid m where
    type Element m :: Type
    uncons :: m -> Maybe (Element m, m)
    singleton :: Element m -> m

And then the laws are basically that it is completely compatible with FactorialMonoid when you use uncons and singleton to roundtrip back and forth between element and container types.

Also on a side note it seems like it would be useful to tie FactorialMonoid into the existing Reductive* hierarchy somehow, particularly since it directly exposes splitPrimePrefix which seems like it should be related to stripPrefix in the form:

stripPrimePrefix c == Just (a, b) ==> stripPrefix a c == Just b

I'm still not 100% sure how this would ideally mesh with the regular Foldable/Functor hierarchy and things like Mono*, but I think the above should still be a nice thing to have and an improvement on focusing specifically on Char.

Consider adding `Right*` instances for `[a]`

I'm assuming you omitted them for perf reasons, but when I'm dealing with short String's or other [a], which is not particularly rare, I really just want to be able to stripSuffix and similar with no hassle. Also time complexity wise the perf is no worse than Text.

Compile failure with base <= 4.10

Despite advertising compatibility with base >= 4.9 && < 5 the most recent release fails with base-4.10 and base-4.9; e.g.

Building library for monoid-subclasses-1.0..
[ 1 of 14] Compiling Data.Monoid.Null ( src/Data/Monoid/Null.hs, dist/build/Data/Monoid/Null.o )
[ 2 of 14] Compiling Data.Semigroup.Cancellative ( src/Data/Semigroup/Cancellative.hs, dist/build/Data/Semigroup/Cancellative.o )
[ 3 of 14] Compiling Data.Monoid.Monus ( src/Data/Monoid/Monus.hs, dist/build/Data/Monoid/Monus.o )
[ 4 of 14] Compiling Data.Monoid.GCD  ( src/Data/Monoid/GCD.hs, dist/build/Data/Monoid/GCD.o )
[ 5 of 14] Compiling Data.Monoid.Cancellative ( src/Data/Monoid/Cancellative.hs, dist/build/Data/Monoid/Cancellative.o )
[ 6 of 14] Compiling Data.Semigroup.Factorial ( src/Data/Semigroup/Factorial.hs, dist/build/Data/Semigroup/Factorial.o )

src/Data/Semigroup/Factorial.hs:423:94: error:
    • Could not deduce (Semigroup b) arising from a use of ‘<>’
      from the context: (Factorial a, Monoid b, Monad m)
        bound by the type signature for:
                   mapM :: forall a b (m :: * -> *).
                           (Factorial a, Monoid b, Monad m) =>
                           (a -> m b) -> a -> m b
        at src/Data/Semigroup/Factorial.hs:422:1-66
      Possible fix:
        add (Semigroup b) to the context of
          the type signature for:
            mapM :: forall a b (m :: * -> *).
                    (Factorial a, Monoid b, Monad m) =>
                    (a -> m b) -> a -> m b
    • In the first argument of ‘Monad.liftM2’, namely ‘(<>)’
      In the first argument of ‘(.)’, namely ‘Monad.liftM2 (<>)’
      In the second argument of ‘(.)’, namely ‘Monad.liftM2 (<>) . f’
    |
423 | mapM f = ($ return mempty) . appEndo . Data.Semigroup.Factorial.foldMap (Endo . Monad.liftM2 (<>) . f)
    |                                                                                              ^^^^

I've already performed a respective Hackage Metadata Revision (see https://hackage.haskell.org/package/monoid-subclasses-1.0/revisions). As far as Hackage is concerned the particular matter of monoid-subclasses-1.0 having inaccurate version bounds has been resolved, specifically there is no need nor any benefit to uploading a new release to Hackage for the sole purpose of tightening the version bounds.

Please let me know if you have any questions!

Support for GHC 7.4.2/containers 0.4

When building Stackage against GHC 7.4.2, I get the following:

monoid-subclasses-0.3.3 (Mario Blazevic [email protected] @blamario) cannot use:

  • containers-0.4.2.1 -- ==0.5.*

Would it be possible to drop the lower bounds on containers to allow monoid-subclasses to be compiled with older containers?

`OverlappingGCDMonoid` instance for `Map` may be unlawful.

Hi @blamario

I was writing some QuickCheck properties for instances of OverlappingGCDMonoid, and I discovered a counterexample for Map that does not appear to satisfy the laws for this class.

I'm testing the following laws (quoted from the documentation):

stripOverlap a b == (stripSuffixOverlap b a, overlap a b, stripPrefixOverlap a b)
stripSuffixOverlap b a <> overlap a b == a
overlap a b <> stripPrefixOverlap a b == b

Here's a minimal counterexample, where two out the three laws are not satisfied:

> import Data.Monoid.GCD
> import qualified Data.Map.Strict as Map
> a = Map.fromList [(0 :: Int, 0 :: Int)]
> b = Map.fromList [(0 :: Int, 1 :: Int)]
> stripOverlap a b == (stripSuffixOverlap b a, overlap a b, stripPrefixOverlap a b)
False
> stripSuffixOverlap b a <> overlap a b == a
True
> overlap a b <> stripPrefixOverlap a b == b
False

Comparing the definition of the instance for Map with the documentation for OverlappingGCDMonoid, I noticed that the positions of stripSuffixOverlap and stripPrefixOverlap have been transposed:

stripOverlap a b == (stripSuffixOverlap b a, overlap a b, stripPrefixOverlap a b)
stripOverlap a b =  (stripPrefixOverlap b a, overlap a b, stripSuffixOverlap a b)

However, even if we were to fix the implementation by reversing the transposition, the following law would still not hold:

> a = Map.fromList [(0 :: Int, 0 :: Int)]
> b = Map.fromList [(0 :: Int, 1 :: Int)]
> overlap a b <> stripPrefixOverlap a b == b
False

I have tried to write a lawful instance for Map, but I'm not yet sure whether it's possible due to the behaviour of <> for Map (among other things).

Though perhaps I've missed something?

By the way, many thanks for writing this library!

Missing licence file

/usr/bin/ar: creating dist/build/libHSmonoid-subclasses-0.1.1.a
cabal: BSD3-LICENSE.txt: does not exist
cabal: Error: some packages failed to install:
monoid-subclasses-0.1.1 failed during the final install step. The exception
was:
ExitFailure 1

Add `Monus` (And `Left`/`Right` variants) class.

Currently it seems like I can't do traditional Set subtraction with any of these classes, monus appears to be a monoidal generalization of this concept.

It should probably be a subclass of ReductiveMonoid as given a hypothetical monus :: Monus m => m -> m -> m, you could implement </> as a </> b = let c = monus a b in bool Nothing c (b <> c == a). However it should not be a subclass of Cancellative as Set is an example of a Monus that isn't Cancellative.

Test suite fails due to `Overlapping instances`

Configuring monoid-subclasses-0.4.0.4...
Dependency QuickCheck ==2.*: using QuickCheck-2.7.6
Dependency base ==4.*: using base-4.7.0.2
Dependency bytestring >=0.9 && <1.0: using bytestring-0.10.4.0
Dependency containers >=0.5.2.0 && <0.6: using containers-0.5.5.1
Dependency monoid-subclasses -any: using monoid-subclasses-0.4.0.4
Dependency primes ==0.2.*: using primes-0.2.1.0
Dependency quickcheck-instances ==0.3.*: using quickcheck-instances-0.3.8
Dependency tasty >=0.7: using tasty-0.10.1
Dependency tasty-quickcheck >=0.7: using tasty-quickcheck-0.8.3.2
Dependency text >=0.11 && <1.3: using text-1.1.1.3
Dependency vector >=0.9 && <0.11: using vector-0.10.12.3
Using Cabal-1.22.1.1 compiled by ghc-7.8
Using compiler: ghc-7.8.4
[..]
Preprocessing test suite 'Main' for monoid-subclasses-0.4.0.4...
[ 1 of 10] Compiling Data.Monoid.Cancellative ( Data/Monoid/Cancellative.hs, dist-ghc/build/Main/Main-tmp/Data/Monoid/Cancellative.o )
[ 2 of 10] Compiling Data.Monoid.Null ( Data/Monoid/Null.hs, dist-ghc/build/Main/Main-tmp/Data/Monoid/Null.o )
[ 3 of 10] Compiling Data.Monoid.Factorial ( Data/Monoid/Factorial.hs, dist-ghc/build/Main/Main-tmp/Data/Monoid/Factorial.o )
[ 4 of 10] Compiling Data.Monoid.Textual ( Data/Monoid/Textual.hs, dist-ghc/build/Main/Main-tmp/Data/Monoid/Textual.o )
[ 5 of 10] Compiling Data.Monoid.Instances.Positioned ( Data/Monoid/Instances/Positioned.hs, dist-ghc/build/Main/Main-tmp/Data/Monoid/Instances/Positioned.o )
[ 6 of 10] Compiling Data.Monoid.Instances.Stateful ( Data/Monoid/Instances/Stateful.hs, dist-ghc/build/Main/Main-tmp/Data/Monoid/Instances/Stateful.o )
[ 7 of 10] Compiling Data.Monoid.Instances.Measured ( Data/Monoid/Instances/Measured.hs, dist-ghc/build/Main/Main-tmp/Data/Monoid/Instances/Measured.o )
[ 8 of 10] Compiling Data.Monoid.Instances.Concat ( Data/Monoid/Instances/Concat.hs, dist-ghc/build/Main/Main-tmp/Data/Monoid/Instances/Concat.o )
[ 9 of 10] Compiling Data.Monoid.Instances.ByteString.UTF8 ( Data/Monoid/Instances/ByteString/UTF8.hs, dist-ghc/build/Main/Main-tmp/Data/Monoid/Instances/ByteString/UTF8.o )
[10 of 10] Compiling Main             ( Test/TestMonoidSubclasses.hs, dist-ghc/build/Main/Main-tmp/Main.o )

Test/TestMonoidSubclasses.hs:452:48:
    Overlapping instances for Show (a -> Bool)
      arising from a use of `property'
    Matching instances:
      instance [safe] Show (a -> b) -- Defined in `Text.Show.Functions'
      instance Show a => Show (a -> Bool)
        -- Defined at Test/TestMonoidSubclasses.hs:841:10
    In the expression: property
    In the expression:
      property $ \ p -> forAll (arbitrary :: Gen a) (check p)
    In an equation for `checkSpan':
        checkSpan (FactorialMonoidInstance (_ :: a))
          = property $ \ p -> forAll (arbitrary :: Gen a) (check p)
          where
              check p a
                = span p a == (mconcat l, mconcat r)
                where
                    (l, r) = List.span p (factors a)

Test/TestMonoidSubclasses.hs:456:53:
    Overlapping instances for Show (Bool -> a -> Maybe Bool)
      arising from a use of `property'
    Matching instances:
      instance [safe] Show (a -> b) -- Defined in `Text.Show.Functions'
      instance Show a => Show (Bool -> a -> Maybe Bool)
        -- Defined at Test/TestMonoidSubclasses.hs:844:10
    In the expression: property
    In the expression:
      property
      $ \ (f, s) -> forAll (arbitrary :: Gen a) (check f (s :: Bool))
    In an equation for `checkSpanMaybe':
        checkSpanMaybe (FactorialMonoidInstance (_ :: a))
          = property
            $ \ (f, s) -> forAll (arbitrary :: Gen a) (check f (s :: Bool))
          where
              check f s0 a
                = a == prefix <> suffix
                  &&
                    foldMaybe prefix == Just s'
                    && (null suffix || f s' (primePrefix suffix) == Nothing)
                where
                    (prefix, suffix, s') = spanMaybe s0 f a
                    foldMaybe = foldl g (Just s0)
                    ....

Test/TestMonoidSubclasses.hs:464:77:
    Overlapping instances for Show (a -> Bool)
      arising from a use of `check'
    Matching instances:
      instance [safe] Show (a -> b) -- Defined in `Text.Show.Functions'
      instance Show a => Show (a -> Bool)
        -- Defined at Test/TestMonoidSubclasses.hs:841:10
    In the second argument of `forAll', namely `check'
    In the expression: forAll (arbitrary :: Gen a) check
    In an equation for `checkSplit':
        checkSplit (FactorialMonoidInstance (_ :: a))
          = forAll (arbitrary :: Gen a) check
          where
              check a
                = property
                    (\ pred -> all (all (not . pred) . factors) (split pred a))
                  .&&.
                    property
                      (\ prime -> mconcat (intersperse prime $ split (== prime) a) == a)

Test/TestMonoidSubclasses.hs:622:4:
    Overlapping instances for Show (Bool -> Char -> Maybe Bool)
      arising from a use of `property'
    Matching instances:
      instance [safe] Show (a -> b) -- Defined in `Text.Show.Functions'
      instance Show a => Show (Bool -> a -> Maybe Bool)
        -- Defined at Test/TestMonoidSubclasses.hs:844:10
    In the expression: property
    In the expression:
      property
      $ \ (ft, fc, s)
          -> forAll (arbitrary :: Gen a) (check ft fc (s :: Bool))
    In an equation for `checkTextualSpanMaybe':
        checkTextualSpanMaybe (TextualMonoidInstance (_ :: a))
          = property
            $ \ (ft, fc, s)
                -> forAll (arbitrary :: Gen a) (check ft fc (s :: Bool))
          where
              check ft fc s0 a
                = a == prefix <> suffix
                  &&
                    foldMaybe prefix == Just s'
                    &&
                      (null suffix
                       ||
                         maybe
                           (ft s' (primePrefix suffix))
                           (fc s')
                           (Textual.characterPrefix suffix)
                         == Nothing)
                where
                    (prefix, suffix, s') = Textual.spanMaybe s0 ft fc a
                    foldMaybe = Textual.foldl gt gc (Just s0)
                    ....

Test/TestMonoidSubclasses.hs:639:4:
    Overlapping instances for Show (Bool -> Char -> Maybe Bool)
      arising from a use of `property'
    Matching instances:
      instance [safe] Show (a -> b) -- Defined in `Text.Show.Functions'
      instance Show a => Show (Bool -> a -> Maybe Bool)
        -- Defined at Test/TestMonoidSubclasses.hs:844:10
    In the expression: property
    In the expression:
      property
      $ \ (fc, s) -> forAll (arbitrary :: Gen a) (check fc (s :: Bool))
    In an equation for `checkTextualSpanMaybe_':
        checkTextualSpanMaybe_ (TextualMonoidInstance (_ :: a))
          = property
            $ \ (fc, s) -> forAll (arbitrary :: Gen a) (check fc (s :: Bool))
          where
              check fc s0 a
                = a == prefix <> suffix
                  &&
                    foldMaybe prefix == Just s'
                    &&
                      (null suffix
                       || (Textual.characterPrefix suffix >>= fc s') == Nothing)
                where
                    (prefix, suffix, s') = Textual.spanMaybe_ s0 fc a
                    foldMaybe = Textual.foldl_ gc (Just s0)
                    ....

Test/TestMonoidSubclasses.hs:648:4:
    Overlapping instances for Show (Bool -> Char -> Maybe Bool)
      arising from a use of `property'
    Matching instances:
      instance [safe] Show (a -> b) -- Defined in `Text.Show.Functions'
      instance Show a => Show (Bool -> a -> Maybe Bool)
        -- Defined at Test/TestMonoidSubclasses.hs:844:10
    In the expression: property
    In the expression:
      property
      $ \ (fc, s) -> forAll (arbitrary :: Gen a) (check fc (s :: Bool))
    In an equation for checkTextualSpanMaybe_':
        checkTextualSpanMaybe_' (TextualMonoidInstance (_ :: a))
          = property
            $ \ (fc, s) -> forAll (arbitrary :: Gen a) (check fc (s :: Bool))
          where
              check fc s0 a
                = a == prefix <> suffix
                  &&
                    foldMaybe prefix == Just s'
                    &&
                      (null suffix
                       || (Textual.characterPrefix suffix >>= fc s') == Nothing)
                where
                    (prefix, suffix, s') = Textual.spanMaybe_' s0 fc a
                    foldMaybe = Textual.foldl_' gc (Just s0)
                    ....

Textual.map test failure

Using Stack + LTS-13 I see the following failure:

$ stack test --ta "--quickcheck-replay=196359 -p Textual.map"
monoid-subclasses-0.4.6.1: test (suite: Main, args: --quickcheck-replay=196359 -p Textual.map)

MonoidSubclasses
  Textual.map:       FAIL (0.08s)
    *** Failed! Exception: 'Prelude.Enum.Char.succ: bad argument' (after 62 tests):
    Line 0, column 0: "\905978^#Bg\1114111\SYN\124052\956452^\NAK\SUBzZ)l)\855554\469442\SUBa\237611\259378vG\292030e-)\US hc\415135-\RSW+A#%"
    Use --quickcheck-replay=196359 to reproduce.
  Textual.mapAccumL: OK (0.21s)
    +++ OK, passed 100 tests.
  Textual.mapAccumR: OK (0.20s)
    +++ OK, passed 100 tests.

1 out of 3 tests failed (0.50s)

monoid-subclasses-0.4.6.1: Test suite Main failed
Test suite failure for package monoid-subclasses-0.4.6.1
    Main:  exited with: ExitFailure 1
Logs printed to console

Awkward performance requirements in class laws

The performance requirements often mention a notion of length even though the class itself doesn't even guarantee that the concept of a length exists at all.

Personally I think performance requirements should be kept out of the laws, and that they should solely focus on equalities / totality.

`Factorial` laws too strong

Specifically Product Natural, sets and maps don't obey the following laws:

factors . reverse == List.reverse . factors
primeSuffix s == primePrefix (reverse s)

It seems like those laws and reverse itself should most likely be moved to StableFactorial.

There are at least four categories of instance that I can see: list-like, set-like, map-like and bag-like (Product Natural). Each of them have various laws that apply to them but not necessarily the others.

Distributivity laws for `GCDMonoid`

Hi @blamario

While reading through the documentation for GCDMonoid, I noticed this statement about distributivity laws:

If a GCDMonoid happens to also be Cancellative, it should additionally satisfy the following laws:

gcd (a <> b) (a <> c) == a <> gcd b c
gcd (a <> c) (b <> c) == gcd a b <> c

This got me thinking: must a GCD monoid be cancellative in order to satisfy the above laws?

Among the types for which instances of GCDMonoid are provided, only the following types are not Cancellative:

  • IntSet
  • Set a
  • Product Natural

But despite non-cancellativity, these types do seem to satisfy the distributivity laws:


Justification

Set types

We can consider the IntSet and Set a types together, as their GCDMonoid and Semigroup instances are defined equivalently:

instance Semigroup IntSet  where (<>) = IntSet.union
instance Semigroup (Set a) where (<>) =    Set.union

instance GCDMonoid IntSet  where gcd = IntSet.intersection
instance GCDMonoid (Set a) where gcd =    Set.intersection

Of course, these types are certainly not Cancellative:

-- In order to be `Cancellative`, we must have:
(a <> b) </> b == Just a

-- If:
a = Set.fromList [0]
b = Set.fromList [0]

-- Then:
(a <> b)       ==       Set.fromList [0]
(a <> b) </> b == Just (Set.fromList [ ])
Just a         == Just (Set.fromList [0])

-- But:
Just (Set.fromList []) /= Just (Set.fromList [0])

However, we can show that they satisfy the distributivity laws.

If we substitute the above instance method definitions into the distributivity laws, we obtain:

intersection (union a b) (union a c) == union a (intersection b c)
intersection (union a c) (union b c) == union (intersection a b) c

Rewriting with traditional set notation, we obtain:

(A ∪ B) ∩ (A ∪ C) = A ∪ (B ∩ C)
(A ∪ B) ∩ (B ∪ C) = (A ∩ B) ∪ C

These are equivalent to the standard distributivity law(s) for sets, which can be proven in a number of different ways.


Product Natural

This type has the following Semigroup and GCDMonoid instances:

instance Semigroup (Product Natural) where (<>) = Prelude.(*)
instance GCDMonoid (Product Natural) where gcd  = Prelude.gcd

This type is also not Cancellative:

-- In order to be `Cancellative`, we must have:
(a <> b) </> b == Just a

-- If:
a = Product 1
b = Product 0

-- Then:         
(a <> b)       ==       Product 0
(a <> b) </> b == Just (Product 0)
Just a         == Just (Product 1)

-- But:
Just (Product 0) /= Just (Product 1)

But again, we can show that Product Natural satisfies the GCD distributivity laws.

If we substitute the instance method definitions above into the distributivity laws, we obtain:

Prelude.gcd (a * b) (a * c) == a * Prelude.gcd b c
Prelude.gcd (a * c) (b * c) == Prelude.gcd a b * c

This is equivalent to the distributivity laws for the standard gcd function on non-zero natural numbers:

gcd (ab, ac) = a * gcd (b, c)
gcd (ac, bc) = gcd (a, b) * c

But what about values of Product 0? Well it turns out that the laws still hold for zero values, since Prelude.gcd is defined such that:

Prelude.gcd 0 a == a
Prelude.gcd a 0 == a

Which means that:

-- When a = 0:
Prelude.gcd (a * b) (a * c) == a * Prelude.gcd b c
Prelude.gcd (0 * b) (0 * c) == 0 * Prelude.gcd b c
Prelude.gcd  0       0      == 0
                     0      == 0 -- True

-- When b = 0:
Prelude.gcd (a * b) (a * c) == a * Prelude.gcd b c
Prelude.gcd (a * 0) (a * c) == a * Prelude.gcd 0 c
Prelude.gcd      0  (a * c) == a *               c
                     a * c  == a *               c -- True

-- When c = 0:
Prelude.gcd (a * b) (a * c) == a * Prelude.gcd b c
Prelude.gcd (a * b) (a * 0) == a * Prelude.gcd b 0
Prelude.gcd (a * b)      0  == a *             b
             a * b          == a *             b -- True

-- When a = 0, b = 0:
Prelude.gcd (a * b) (a * c) == a * Prelude.gcd b c
Prelude.gcd (0 * 0) (0 * c) == 0 * Prelude.gcd 0 c
Prelude.gcd  0       0      == 0 *             0
             0              == 0 -- True

-- When a = 0, c = 0:
Prelude.gcd (a * b) (a * c) == a * Prelude.gcd b c
Prelude.gcd (0 * b) (a * 0) == 0 * Prelude.gcd b 0
Prelude.gcd  0           0  == 0 *             b
             0              == 0 -- True

-- When b = 0, c = 0:
Prelude.gcd (a * b) (a * c) == a * Prelude.gcd b c
Prelude.gcd (a * 0) (a * 0) == a * Prelude.gcd 0 0
Prelude.gcd      0       0  == a *             0
                 0          ==                 0 -- True

-- When a = 0, b = 0, c = 0:
Prelude.gcd (a * b) (a * c) == a * Prelude.gcd b c
Prelude.gcd (0 * 0) (0 * 0) == 0 * Prelude.gcd 0 0
Prelude.gcd  0       0      == 0
             0              == 0 -- True

Proposal

Given that all the provided instances of GCDMonoid do appear to satisfy the distributivity laws (regardless of whether or not they are Cancellative), perhaps could we simply revise the documentation for GCDMonoid to state this as a requirement, namely that all instances must satisfy the distributivity laws?

Advantages:

  • Distributivity is a very useful property to have for gcd-like operations. It seems a shame that library writers can only rely on this property for the subset of GCDMonoid types that are also Cancellative.
  • It would make the GCDMonoid type class more consistent with LCMMonoid, which does require that all instances satisfy the distributivity laws.
  • We can remove a case of laws that are conditional on a type being instances of two classes, which seems to be a nice bonus.

I did a search over all code in GitHub that uses monoid-subclasses, but couldn't find an instance (out in the wild) that would violate these laws. I also tested all the provided instances extensively against a suite of property tests with coverage checks, and could not find a counterexample.

But perhaps I'm overlooking something? Perhaps there a good reason to keep this restriction?

If you agree this is a worthwhile simplification, let me know -- I'd be happy to make a PR that adjusts the tests and documentation. 👍🏻

Thanks for reading through, and best wishes!

Jonathan

New Release?

Hi @blamario

I'm wondering if it's possible to make a new release to hackage?

AFAICT, the latest release (1.2) still suffers from issue #38, which fortunately we fixed in #39. (Many thanks for merging that PR!)

I'm writing a library that depends on the Monus instance for Maybe. For the moment, I can easily work around the issue by using a source-repository-package stanza that links directly to this repository, but it would be great to link to an updated hackage version instead.

If you're too busy, then no worries. If I can help in any way, would be happy to.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.