lawcheck 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: Bug report
3
+ about: Report a bug in lawcheck
4
+ title: "[bug] "
5
+ labels: bug
6
+ ---
7
+
8
+ **Describe the bug**
9
+ A clear description of what the bug is.
10
+
11
+ **Minimal reproducer**
12
+ ```python
13
+ # Paste the smallest code that triggers the bug
14
+ ```
15
+
16
+ **Expected behavior**
17
+ What you expected to happen.
18
+
19
+ **Actual behavior**
20
+ What actually happened, including any tracebacks.
21
+
22
+ **Environment**
23
+ - lawcheck version:
24
+ - Python version:
25
+ - hypothesis version:
26
+ - OS:
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest a new law suite or improvement
4
+ title: "[feature] "
5
+ labels: enhancement
6
+ ---
7
+
8
+ **What algebraic law or feature would you like added?**
9
+ Describe the law and why it would be useful.
10
+
11
+ **Example usage**
12
+ ```python
13
+ # How would you use this feature?
14
+ ```
15
+
16
+ **Are you willing to implement it?**
17
+ Yes / No / Maybe.
@@ -0,0 +1,22 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python: ["3.10", "3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python }}
19
+ - run: pip install -e ".[dev]"
20
+ - run: ruff check .
21
+ - run: mypy src
22
+ - run: pytest -q
@@ -0,0 +1,40 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ *.egg
7
+ *.egg-info/
8
+ dist/
9
+ build/
10
+ .eggs/
11
+ .pytest_cache/
12
+ .mypy_cache/
13
+ .ruff_cache/
14
+ .hypothesis/
15
+ *.coverage
16
+ .coverage
17
+ htmlcov/
18
+
19
+ # Virtual environments
20
+ .venv/
21
+ venv/
22
+ env/
23
+
24
+ # Distribution / packaging
25
+ *.tar.gz
26
+ *.whl
27
+
28
+ # Editor
29
+ .vscode/
30
+ .idea/
31
+ *.swp
32
+ *.swo
33
+ *~
34
+
35
+ # OS
36
+ .DS_Store
37
+ Thumbs.db
38
+
39
+ # uv lockfile (library: not committed)
40
+ uv.lock
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ All notable changes to lawcheck are documented in this file.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
+ lawcheck uses [semantic versioning](https://semver.org/).
7
+
8
+ ## [0.2.0] - 2026-06-17
9
+
10
+ ### Added
11
+
12
+ - Applicative laws: four pure primitives (`holds_applicative_identity`,
13
+ `holds_applicative_homomorphism`, `holds_applicative_interchange`,
14
+ `holds_applicative_composition`) and their `assert_*` counterparts.
15
+ - `verify_applicative` Hypothesis runner: verifies all four applicative laws
16
+ given `pure`, `ap`, a homomorphism function, and three strategies.
17
+ - The applicative law suite accepts `pure: Callable[[object], object]` and
18
+ `ap: Callable[[object, object], object]`, consistent with the existing
19
+ functor/monad object-typed signatures.
20
+ - `eq` is required everywhere; no default.
21
+ - Meta-tests: Maybe extended with `maybe_pure` and `maybe_ap`; lawful Maybe
22
+ passes all four laws; `broken_ap` (always returns Nothing) is caught by
23
+ identity and homomorphism; custom eq oracle is honored.
24
+
25
+ ### Note
26
+
27
+ PyPI publication is pending new-project quota approval. The dist artifact
28
+ is built and twine-checked; publication will proceed once the quota clears.
29
+
30
+ ## [0.1.0] - 2026-06-17
31
+
32
+ ### Added
33
+
34
+ - Pure assertion primitives for semigroup, monoid, commutativity, functor, and monad laws.
35
+ - Hypothesis-powered runners: `verify_semigroup`, `verify_monoid`, `verify_commutative`, `verify_functor`, `verify_monad`.
36
+ - Pluggable equality oracle: `eq` is required everywhere, no default.
37
+ - Oracle helpers: `value_eq`, `by_repr_eq`, `normalized_eq`, `lifted_eq`.
38
+ - Self-verifying meta-tests: lawful structures pass, unlawful structures are caught.
39
+ - Full strict mypy typing, `py.typed` marker.
40
+ - Example script (`examples/int_monoid.py`) demonstrating both layers.
41
+ - CI: pytest, ruff, mypy on Python 3.10-3.13.
@@ -0,0 +1,69 @@
1
+ # lawcheck - CLAUDE.md
2
+
3
+ ## Project overview
4
+
5
+ lawcheck is a property-based algebraic law testing library for Python. It provides ready-made Hypothesis law suites for semigroups, monoids, commutative structures, functors, and monads, with a pluggable equality oracle. The equality oracle is the central design point: `eq` is always required, never defaulted.
6
+
7
+ ## Stack
8
+
9
+ - Python 3.10+
10
+ - Build: hatchling, src layout
11
+ - Runtime dep: hypothesis only
12
+ - Dev: pytest, ruff, mypy
13
+ - Package manager: uv
14
+
15
+ ## Commands
16
+
17
+ ```bash
18
+ uv venv
19
+ uv pip install -e ".[dev]"
20
+ uv run pytest -q
21
+ uv run ruff check .
22
+ uv run mypy src
23
+ uv build
24
+ uv run --with twine twine check dist/*
25
+ ```
26
+
27
+ ## Module layout
28
+
29
+ ```
30
+ src/lawcheck/
31
+ __init__.py # public API, __all__
32
+ _types.py # TypeVar aliases
33
+ primitives.py # holds_* and assert_* (no Hypothesis)
34
+ runners.py # verify_* (Hypothesis-powered)
35
+ oracle.py # equality oracle helpers
36
+ py.typed
37
+ tests/
38
+ maybe.py # minimal Maybe type for tests only
39
+ conftest.py # Hypothesis profile
40
+ test_semigroup.py
41
+ test_monoid.py
42
+ test_commutative.py
43
+ test_functor.py
44
+ test_monad.py
45
+ test_oracle.py
46
+ examples/
47
+ int_monoid.py
48
+ ```
49
+
50
+ ## Key design rules
51
+
52
+ 1. `eq` is required at every call site; no default.
53
+ 2. No default parameter values anywhere. Use a second named function instead.
54
+ 3. Pure primitives (`holds_*`, `assert_*`) in primitives.py; Hypothesis runners (`verify_*`) in runners.py.
55
+ 4. No em dash characters anywhere.
56
+ 5. Strict mypy, ruff clean.
57
+ 6. Git author: Amaar Chughtai, no Co-authored-by trailers.
58
+
59
+ ## Adding a new law suite
60
+
61
+ 1. Add `holds_*` and `assert_*` to `primitives.py`.
62
+ 2. Add `verify_*` to `runners.py`.
63
+ 3. Export from `__init__.py`, add to `__all__`.
64
+ 4. Add meta-tests: one lawful pass, one unlawful fail.
65
+
66
+ ## Deferred
67
+
68
+ - Applicative laws (next addition after v0.1.0).
69
+ - pytest plugin for auto law collection.
@@ -0,0 +1,28 @@
1
+ # Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as contributors and maintainers pledge to make participation in lawcheck a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ ## Our Standards
8
+
9
+ Examples of behavior that contributes to a positive environment:
10
+
11
+ - Using welcoming and inclusive language
12
+ - Being respectful of differing viewpoints and experiences
13
+ - Gracefully accepting constructive criticism
14
+ - Focusing on what is best for the community
15
+
16
+ Examples of unacceptable behavior:
17
+
18
+ - Harassment, insults, or derogatory comments
19
+ - Publishing private information without explicit permission
20
+ - Other conduct that could reasonably be considered inappropriate
21
+
22
+ ## Enforcement
23
+
24
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting the maintainers directly. All complaints will be reviewed and investigated promptly.
25
+
26
+ ## Attribution
27
+
28
+ This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
@@ -0,0 +1,36 @@
1
+ # Contributing
2
+
3
+ Contributions are welcome. Please open an issue first to discuss significant changes.
4
+
5
+ ## Setup
6
+
7
+ ```
8
+ git clone https://github.com/amaar-mc/lawcheck
9
+ cd lawcheck
10
+ uv venv
11
+ uv pip install -e ".[dev]"
12
+ ```
13
+
14
+ ## Running checks
15
+
16
+ ```
17
+ uv run pytest -q
18
+ uv run ruff check .
19
+ uv run mypy src
20
+ ```
21
+
22
+ All three must pass before opening a pull request.
23
+
24
+ ## Adding a new law suite
25
+
26
+ 1. Add pure `holds_*` and `assert_*` primitives to `src/lawcheck/primitives.py`.
27
+ 2. Add a `verify_*` runner to `src/lawcheck/runners.py`.
28
+ 3. Export from `src/lawcheck/__init__.py` and add to `__all__`.
29
+ 4. Add meta-tests: at least one lawful case and one unlawful case.
30
+
31
+ ## Style
32
+
33
+ - Python 3.10+, strict mypy, ruff.
34
+ - No default parameter values. Provide a second named function where ergonomics require it.
35
+ - No em dash characters in code, comments, or commit messages.
36
+ - Commit format: `type(scope): description`.
lawcheck-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Amaar Chughtai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,185 @@
1
+ Metadata-Version: 2.4
2
+ Name: lawcheck
3
+ Version: 0.2.0
4
+ Summary: Property-based algebraic law testing for Python: Hypothesis-driven law suites for semigroups, monoids, functors, applicatives, and monads, with a pluggable equality oracle.
5
+ Project-URL: Homepage, https://github.com/amaar-mc/lawcheck
6
+ Project-URL: Repository, https://github.com/amaar-mc/lawcheck
7
+ Project-URL: Issues, https://github.com/amaar-mc/lawcheck/issues
8
+ Project-URL: Changelog, https://github.com/amaar-mc/lawcheck/blob/main/CHANGELOG.md
9
+ Author-email: Amaar Chughtai <amaardevx@gmail.com>
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 Amaar Chughtai
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: algebra,applicative,functor,hypothesis,laws,monad,monoid,property-based-testing,semigroup,testing
33
+ Classifier: Development Status :: 4 - Beta
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Operating System :: OS Independent
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Programming Language :: Python :: 3.10
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Classifier: Programming Language :: Python :: 3.13
42
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
43
+ Classifier: Topic :: Software Development :: Testing
44
+ Classifier: Typing :: Typed
45
+ Requires-Python: >=3.10
46
+ Requires-Dist: hypothesis>=6.0.0
47
+ Provides-Extra: dev
48
+ Requires-Dist: hypothesis>=6.0.0; extra == 'dev'
49
+ Requires-Dist: mypy>=1.9.0; extra == 'dev'
50
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
51
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
52
+ Description-Content-Type: text/markdown
53
+
54
+ # lawcheck
55
+
56
+ <p align="center">
57
+ <img src="assets/logo.png" alt="lawcheck logo" width="160">
58
+ </p>
59
+
60
+ Property-based algebraic law testing for Python. Ready-made [Hypothesis](https://hypothesis.readthedocs.io) law suites for semigroups, monoids, commutative structures, functors, applicatives, and monads, with a pluggable equality oracle.
61
+
62
+ ## The problem
63
+
64
+ Algebraic laws (associativity, identity, functor composition, monad associativity) are easy to state but hard to test systematically. You want Hypothesis to search for counterexamples, not to write the laws yourself every time.
65
+
66
+ The bigger problem: **what does "equal" mean for your type?**
67
+
68
+ For `int` it's obvious. For `IO[A]`, `Task`, async wrappers, or any effectful type, two logically equal values are often physically distinct objects. Python's `==` is wrong or absent. The [cats-effect](https://typelevel.org/cats-effect/) Scala library confronted this exactly and introduced a formal `Eq` type-class hierarchy. lawcheck asks you to supply `eq` explicitly so the meaning of "equal" is always unambiguous.
69
+
70
+ ## Install
71
+
72
+ ```
73
+ pip install lawcheck
74
+ ```
75
+
76
+ Sole runtime dependency: `hypothesis`.
77
+
78
+ ## Usage
79
+
80
+ ### Layer 1: pure primitives (no Hypothesis, deterministic)
81
+
82
+ ```python
83
+ import operator
84
+ from lawcheck import holds_associative, assert_associative
85
+
86
+ # Check one triple
87
+ holds_associative(operator.add, 1, 2, 3, eq=operator.eq) # True
88
+ holds_associative(operator.sub, 1, 2, 3, eq=operator.eq) # False
89
+
90
+ # Assert or raise with a precise message
91
+ assert_associative(operator.sub, 1, 2, 3, eq=operator.eq)
92
+ # AssertionError: Associativity violated: (op(1, 2)) op 3 = -4, but 1 op (op(2, 3)) = 2
93
+ ```
94
+
95
+ ### Layer 2: Hypothesis runners (search for counterexamples)
96
+
97
+ ```python
98
+ import operator
99
+ from hypothesis import strategies as st
100
+ from lawcheck import verify_monoid, verify_commutative, verify_semigroup
101
+
102
+ # Passes for int addition (associative, left and right identity at 0, commutative)
103
+ verify_monoid(operator.add, 0, strategy=st.integers(), eq=operator.eq)
104
+ verify_commutative(operator.add, strategy=st.integers(), eq=operator.eq)
105
+
106
+ # Finds a counterexample for subtraction
107
+ verify_semigroup(operator.sub, strategy=st.integers(), eq=operator.eq)
108
+ # Raises with Hypothesis's shrunk minimal counterexample
109
+ ```
110
+
111
+ ### Functor, applicative, and monad laws
112
+
113
+ ```python
114
+ from lawcheck import verify_functor, verify_applicative, verify_monad
115
+
116
+ # fmap: (f: A -> B, fa: F[A]) -> F[B]
117
+ verify_functor(
118
+ my_fmap,
119
+ f=lambda x: x * 2,
120
+ g=lambda x: x + 1,
121
+ strategy=my_strategy,
122
+ eq=my_eq,
123
+ )
124
+
125
+ # pure: A -> F[A]; ap: (F[A -> B], F[A]) -> F[B]
126
+ verify_applicative(
127
+ my_pure,
128
+ my_ap,
129
+ lambda x: x * 2,
130
+ value_strategy=st.integers(),
131
+ functor_strategy=my_fa_strategy,
132
+ ap_strategy=my_fn_strategy,
133
+ eq=my_eq,
134
+ )
135
+
136
+ verify_monad(
137
+ ret=my_return,
138
+ bind=my_bind,
139
+ f_arrow=lambda x: my_return(x + 1),
140
+ g_arrow=lambda x: my_return(x * 2),
141
+ strategy=st.integers(),
142
+ monad_strategy=my_monad_strategy,
143
+ eq=my_eq,
144
+ )
145
+ ```
146
+
147
+ ### Pluggable equality oracle
148
+
149
+ ```python
150
+ from lawcheck import normalized_eq, lifted_eq, value_eq
151
+
152
+ # Compare after normalization (e.g. abs value, canonical form)
153
+ abs_eq = normalized_eq(abs)
154
+
155
+ # Unwrap a container and compare inner values
156
+ box_eq = lifted_eq(lambda b: b.value, operator.eq)
157
+
158
+ # Plain == (correct for ints, strings, lists)
159
+ value_eq(1, 1) # True
160
+ ```
161
+
162
+ ## Laws implemented
163
+
164
+ | Structure | Laws |
165
+ |-----------|------|
166
+ | Semigroup | Associativity: `(a op b) op c == a op (b op c)` |
167
+ | Monoid | Associativity + left identity + right identity |
168
+ | Commutative | `a op b == b op a` |
169
+ | Functor | Identity: `fmap(id) == id`; Composition: `fmap(f.g) == fmap(f).fmap(g)` |
170
+ | Applicative | Identity: `ap(pure(id), v) == v`; Homomorphism: `ap(pure(f), pure(x)) == pure(f(x))`; Interchange: `ap(u, pure(y)) == ap(pure(lambda f: f(y)), u)`; Composition: `ap(ap(ap(pure(compose), u), v), w) == ap(u, ap(v, w))` |
171
+ | Monad | Left identity, right identity, associativity of bind |
172
+
173
+ ## The `eq` parameter is required everywhere
174
+
175
+ lawcheck has no default for `eq`. Where ergonomics would tempt a default, there is an explicit second function instead (e.g., `value_eq` for plain types). This is by design: the cats-effect equality-oracle problem is real, and baking in `==` would silently produce wrong answers for effects, async types, and custom containers.
176
+
177
+ See [`docs/architecture.md`](docs/architecture.md) for the full design rationale.
178
+
179
+ ## Contributing
180
+
181
+ See [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).
182
+
183
+ ## License
184
+
185
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,132 @@
1
+ # lawcheck
2
+
3
+ <p align="center">
4
+ <img src="assets/logo.png" alt="lawcheck logo" width="160">
5
+ </p>
6
+
7
+ Property-based algebraic law testing for Python. Ready-made [Hypothesis](https://hypothesis.readthedocs.io) law suites for semigroups, monoids, commutative structures, functors, applicatives, and monads, with a pluggable equality oracle.
8
+
9
+ ## The problem
10
+
11
+ Algebraic laws (associativity, identity, functor composition, monad associativity) are easy to state but hard to test systematically. You want Hypothesis to search for counterexamples, not to write the laws yourself every time.
12
+
13
+ The bigger problem: **what does "equal" mean for your type?**
14
+
15
+ For `int` it's obvious. For `IO[A]`, `Task`, async wrappers, or any effectful type, two logically equal values are often physically distinct objects. Python's `==` is wrong or absent. The [cats-effect](https://typelevel.org/cats-effect/) Scala library confronted this exactly and introduced a formal `Eq` type-class hierarchy. lawcheck asks you to supply `eq` explicitly so the meaning of "equal" is always unambiguous.
16
+
17
+ ## Install
18
+
19
+ ```
20
+ pip install lawcheck
21
+ ```
22
+
23
+ Sole runtime dependency: `hypothesis`.
24
+
25
+ ## Usage
26
+
27
+ ### Layer 1: pure primitives (no Hypothesis, deterministic)
28
+
29
+ ```python
30
+ import operator
31
+ from lawcheck import holds_associative, assert_associative
32
+
33
+ # Check one triple
34
+ holds_associative(operator.add, 1, 2, 3, eq=operator.eq) # True
35
+ holds_associative(operator.sub, 1, 2, 3, eq=operator.eq) # False
36
+
37
+ # Assert or raise with a precise message
38
+ assert_associative(operator.sub, 1, 2, 3, eq=operator.eq)
39
+ # AssertionError: Associativity violated: (op(1, 2)) op 3 = -4, but 1 op (op(2, 3)) = 2
40
+ ```
41
+
42
+ ### Layer 2: Hypothesis runners (search for counterexamples)
43
+
44
+ ```python
45
+ import operator
46
+ from hypothesis import strategies as st
47
+ from lawcheck import verify_monoid, verify_commutative, verify_semigroup
48
+
49
+ # Passes for int addition (associative, left and right identity at 0, commutative)
50
+ verify_monoid(operator.add, 0, strategy=st.integers(), eq=operator.eq)
51
+ verify_commutative(operator.add, strategy=st.integers(), eq=operator.eq)
52
+
53
+ # Finds a counterexample for subtraction
54
+ verify_semigroup(operator.sub, strategy=st.integers(), eq=operator.eq)
55
+ # Raises with Hypothesis's shrunk minimal counterexample
56
+ ```
57
+
58
+ ### Functor, applicative, and monad laws
59
+
60
+ ```python
61
+ from lawcheck import verify_functor, verify_applicative, verify_monad
62
+
63
+ # fmap: (f: A -> B, fa: F[A]) -> F[B]
64
+ verify_functor(
65
+ my_fmap,
66
+ f=lambda x: x * 2,
67
+ g=lambda x: x + 1,
68
+ strategy=my_strategy,
69
+ eq=my_eq,
70
+ )
71
+
72
+ # pure: A -> F[A]; ap: (F[A -> B], F[A]) -> F[B]
73
+ verify_applicative(
74
+ my_pure,
75
+ my_ap,
76
+ lambda x: x * 2,
77
+ value_strategy=st.integers(),
78
+ functor_strategy=my_fa_strategy,
79
+ ap_strategy=my_fn_strategy,
80
+ eq=my_eq,
81
+ )
82
+
83
+ verify_monad(
84
+ ret=my_return,
85
+ bind=my_bind,
86
+ f_arrow=lambda x: my_return(x + 1),
87
+ g_arrow=lambda x: my_return(x * 2),
88
+ strategy=st.integers(),
89
+ monad_strategy=my_monad_strategy,
90
+ eq=my_eq,
91
+ )
92
+ ```
93
+
94
+ ### Pluggable equality oracle
95
+
96
+ ```python
97
+ from lawcheck import normalized_eq, lifted_eq, value_eq
98
+
99
+ # Compare after normalization (e.g. abs value, canonical form)
100
+ abs_eq = normalized_eq(abs)
101
+
102
+ # Unwrap a container and compare inner values
103
+ box_eq = lifted_eq(lambda b: b.value, operator.eq)
104
+
105
+ # Plain == (correct for ints, strings, lists)
106
+ value_eq(1, 1) # True
107
+ ```
108
+
109
+ ## Laws implemented
110
+
111
+ | Structure | Laws |
112
+ |-----------|------|
113
+ | Semigroup | Associativity: `(a op b) op c == a op (b op c)` |
114
+ | Monoid | Associativity + left identity + right identity |
115
+ | Commutative | `a op b == b op a` |
116
+ | Functor | Identity: `fmap(id) == id`; Composition: `fmap(f.g) == fmap(f).fmap(g)` |
117
+ | Applicative | Identity: `ap(pure(id), v) == v`; Homomorphism: `ap(pure(f), pure(x)) == pure(f(x))`; Interchange: `ap(u, pure(y)) == ap(pure(lambda f: f(y)), u)`; Composition: `ap(ap(ap(pure(compose), u), v), w) == ap(u, ap(v, w))` |
118
+ | Monad | Left identity, right identity, associativity of bind |
119
+
120
+ ## The `eq` parameter is required everywhere
121
+
122
+ lawcheck has no default for `eq`. Where ergonomics would tempt a default, there is an explicit second function instead (e.g., `value_eq` for plain types). This is by design: the cats-effect equality-oracle problem is real, and baking in `==` would silently produce wrong answers for effects, async types, and custom containers.
123
+
124
+ See [`docs/architecture.md`](docs/architecture.md) for the full design rationale.
125
+
126
+ ## Contributing
127
+
128
+ See [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).
129
+
130
+ ## License
131
+
132
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,13 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ |---------|-----------|
7
+ | 0.1.x | Yes |
8
+
9
+ ## Reporting a Vulnerability
10
+
11
+ lawcheck is a testing utility library with no network access, no credential handling, and no execution of untrusted code. Security vulnerabilities are unlikely but not impossible (e.g., a dependency vulnerability in hypothesis).
12
+
13
+ To report a vulnerability, open a GitHub issue marked "security" or contact the maintainer at amaardevx@gmail.com. You will receive a response within 7 days.
File without changes
Binary file