blamario / monoid-subclasses Goto Github PK
View Code? Open in Web Editor NEWSubclasses of Monoid with a solid theoretical foundation and practical purposes
License: BSD 3-Clause "New" or "Revised" License
Subclasses of Monoid with a solid theoretical foundation and practical purposes
License: BSD 3-Clause "New" or "Revised" License
I suppose it should be
Subclasses of Monoid with a solid theoretical foundation and practical purposes
The README states that
Every group (i.e., every
Monoid a
with the operationinverse :: a -> a
) is aCancellativeMonoid
wherea </> b = Just (a <> inverse b)
but not everyCancellativeMonoid
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.
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)
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)
|
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
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...
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)
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
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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
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
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.
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
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.
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?)
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
Given certain typeclass constraints, it is already possible to compute an LCM. For example:
GCDMonoid
and Monus
:
lcm a b = (a <\> b) <> gcd a b <> (b <\> a)
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
LCMMonoid
in a separate libraryIf 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
.
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!
Adding a law seems unfortunate without also adding a method. And the method is the obvious one!
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.
> /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)
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.
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.
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)
| ^^^^
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
.
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
.
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!
When building Stackage against GHC 7.4.2, I get the following:
monoid-subclasses-0.3.3 (Mario Blazevic [email protected] @blamario) cannot use:
Would it be possible to drop the lower bounds on containers to allow monoid-subclasses to be compiled with older containers?
Please raise the upper bound on containers.
Contributes to commercialhaskell/stackage#4008
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!
/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
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
.
Hi @blamario
Just wondering if you're up for making a new release to hackage with recent changes?
I can also release an update to quickcheck-monoid-subclasses
with a Laws
definition for LCMMonoid
. (patch: 1cc01127)
By the way, I took the trouble to repair some dead links within README.md
in #44. Hope that's okay!
Cheers!
Jonathan
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)
....
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
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.
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.
Hi @blamario
While reading through the documentation for GCDMonoid
, I noticed this statement about distributivity laws:
If a
GCDMonoid
happens to also beCancellative
, 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:
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
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:
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
.GCDMonoid
type class more consistent with LCMMonoid
, which does require that all instances satisfy the distributivity laws.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
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.