Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added All and Any monoids #397

Merged
merged 2 commits into from
Aug 27, 2024
Merged

Conversation

coot
Copy link
Contributor

@coot coot commented Apr 26, 2024

All is a monoid build around .&&.. It is useful when writing
complex properties which check multiple conditions. Since it is
a monoid it allows one to use foldMap which is often much more
ergonomic than using conjoin.

All satisfies monoid laws up to isSuccess, unless one is using
checkCoverage & cover. I'd argue this is not a problem since
checkCoverage and cover are most often added at the top of the
property.

This patch also adds Any monoid build around .||..

Tests are also included.

@MaximilianAlgehed
Copy link
Collaborator

C.f. #279.

@MaximilianAlgehed
Copy link
Collaborator

MaximilianAlgehed commented Apr 26, 2024

The tricky thing here is deciding if this is the right point on the tradeoff spectrum. Maybe having Monoid Property and Any as an option is another good option? Another problem here is that the names Any and All are taken in base.

The build also fails on a bunch of older GHC versions (and Hugs) but thats not a huge deal - it should be pretty easy to address.

@@ -315,8 +315,10 @@ module Test.QuickCheck
, (.&.)
, (.&&.)
, conjoin
, All (..)
Copy link
Contributor

@phadej phadej Apr 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These re-exports will for sure cause pain down the line. Without re-exporting from the main module the names are less of an issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. It's better to remove them.

@coot
Copy link
Contributor Author

coot commented Apr 26, 2024

The tricky thing here is deciding if this is the right point on the tradeoff spectrum.

Note that All is more general than Property, as it is an existential type which can hold any Testable p => p. When I discussed this with John, we came up with this design as it gives a lot of flexibility in writing things like Any p <> label ..., or even Any p .||. Any p' etc. I am not sure how limiting p :: Property is in general (at least, I don't usually write code which is polymorphic with Testable constraint), but in other areas, e.g. for teaching purposes, people are taking advantage of it, so students do not need to write type annotations, or modify them when playing with code.

-- Note: monoid laws are satisfied up to `isSuccess` unless one is using
-- `checkCoverage`.
--
data All = forall p. Testable p => All { getAll :: p }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe AllOf / AnyOf or AllHold / AnyHolds etc.? To unclash with Data.Monoid.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe And and Or.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another idea: Every / Some

src/Test/QuickCheck/Monoids.hs Outdated Show resolved Hide resolved
src/Test/QuickCheck/Monoids.hs Outdated Show resolved Hide resolved
Comment on lines 36 to 54
-- Use `property @Any` as an accessor which doesn't leak
-- existential variables.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what this means, can you give an example / explanation here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider you have an abient function which returns Property, if you try to use getAny GHC will complain that forall p. Testable p => p is not Property, or it might error by saying that an existential p is leaking, in that scenario just using property (or property . getAny) is what one needs.

λ a  = All True
λ :t a
a :: All
λ :t getAll a

<interactive>:1:1: error: [GHC-55876]
    • Cannot use record selector ‘getAll’ as a function due to escaped type variables
    • In the expression: getAll a
    Suggested fix: Use pattern-matching syntax instead
    
λ :t case a of All x -> x
<interactive>:1:20: error: [GHC-25897]
    • Couldn't match expected type ‘p’ with actual type ‘p1’
      ‘p1’ is a rigid type variable bound by
        a pattern with constructor:
          All :: forall p. Testable p => p -> All,
        in a case alternative
        at <interactive>:1:11-15
      ‘p’ is a rigid type variable bound by
        the inferred type of it :: p
        at <interactive>:1:1
    • In the expression: x
      In a case alternative: All x -> x
      In the expression: case a of All x -> x
    • Relevant bindings include x :: p1 (bound at <interactive>:1:15)

but

λ :t property a
property a :: Property

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see what you mean. I'm inclined to say at this point that it's better to just not say anything / point out that the types are Testable.

Also, the type of getEvery and getSome are such that they can basically never be used. I'd prefer to remove them from the code all together to keep it cleaner.

Comment on lines 14 to 20
-- Use `property @All` as an accessor which doesn't leak
-- existential variables.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what this means, please give an example.

tests/Monoids.hs Outdated Show resolved Hide resolved
tests/Monoids.hs Outdated
Comment on lines 35 to 36
-- , pure $ Any (checkCoverage $ cover 100 False "" True)
-- , pure $ Any (checkCoverage $ cover 100 False "" False)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
-- , pure $ Any (checkCoverage $ cover 100 False "" True)
-- , pure $ Any (checkCoverage $ cover 100 False "" False)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, when I included them, all tests pass - unlike for All / Every where the unit property fails.

@coot
Copy link
Contributor Author

coot commented Jun 4, 2024

@MaximilianAlgehed I addressed your comments.

@MaximilianAlgehed
Copy link
Collaborator

MaximilianAlgehed commented Jun 4, 2024

Great! Please fix the build failures and I'll have a look next week! (OOTO this week)

@coot
Copy link
Contributor Author

coot commented Jul 9, 2024

@MaximilianAlgehed could you approve the workflows to run?

@coot
Copy link
Contributor Author

coot commented Jul 9, 2024

Something went terribly wrong with all the linux jobs:

Run actions/checkout@v3
/usr/bin/docker exec  f8c8e73e602a1181103553ab346548bbea1153df1ee39cd8bca903f874d0dd8f sh -c "cat /etc/*release | grep ^ID"
/__e/node20/bin/node: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.28' not found (required by /__e/node20/bin/node)

@coot
Copy link
Contributor Author

coot commented Jul 9, 2024

Let's try to re-run them.

@MaximilianAlgehed
Copy link
Collaborator

@coot I'm working on a fix for the CI issue in #412

@MaximilianAlgehed
Copy link
Collaborator

@coot sorry for the delay but the fix has been merged

@coot
Copy link
Contributor Author

coot commented Jul 24, 2024

I rebased this PR.

@MaximilianAlgehed
Copy link
Collaborator

Ok, sorry... You need to rebase again because I screwed up with the semantics of github actions and the difference between "run on push" and "run on PR". There is no neat way to get "run on push and also run on PR from forks"... Anway, hopefully this works for now.

@coot
Copy link
Contributor Author

coot commented Jul 24, 2024

Done.

`All` is a monoid build around `.&&.`. It is useful when writing
complex properties which check multiple conditions.  Since it is
a monoid it allows one to use `foldMap` which is often much more
ergonomic than using `conjoin`.

`All` satisfies `monoid` laws up to `isSuccess`, unless one is using
`checkCoverage` & `cover`.  I'd argue this is not a problem since
`checkCoverage` and `cover` are most often added at the top of the
property.

This patch also adds `Any` monoid build around `.||.`.

Tests are also included.
@coot
Copy link
Contributor Author

coot commented Aug 22, 2024

Let's see if I fixed 7.10.3. I was getting a linking error with ghc-7.10.3 locally.

@MaximilianAlgehed MaximilianAlgehed merged commit d66336c into nick8325:master Aug 27, 2024
52 checks passed
@coot coot deleted the coot/monoids branch August 28, 2024 06:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants