pythonic-fp-booleans 3.0.2__tar.gz → 4.1.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.
- {pythonic_fp_booleans-3.0.2 → pythonic_fp_booleans-4.1.0}/PKG-INFO +23 -28
- pythonic_fp_booleans-4.1.0/README.md +24 -0
- {pythonic_fp_booleans-3.0.2 → pythonic_fp_booleans-4.1.0}/pyproject.toml +34 -25
- {pythonic_fp_booleans-3.0.2 → pythonic_fp_booleans-4.1.0}/src/pythonic_fp/booleans/__init__.py +7 -20
- pythonic_fp_booleans-4.1.0/src/pythonic_fp/booleans/flavored.py +194 -0
- pythonic_fp_booleans-4.1.0/src/pythonic_fp/booleans/subtypable.py +246 -0
- pythonic_fp_booleans-4.1.0/src/pythonic_fp/booleans/truthy_falsy.py +229 -0
- pythonic_fp_booleans-3.0.2/.github/workflows/static.yml +0 -59
- pythonic_fp_booleans-3.0.2/.gitignore +0 -11
- pythonic_fp_booleans-3.0.2/CHANGELOG.rst +0 -104
- pythonic_fp_booleans-3.0.2/README.rst +0 -29
- pythonic_fp_booleans-3.0.2/docs/Makefile +0 -69
- pythonic_fp_booleans-3.0.2/docs/build/.nojekyll +0 -0
- pythonic_fp_booleans-3.0.2/docs/build/index.html +0 -44
- pythonic_fp_booleans-3.0.2/docs/build/style.css +0 -44
- pythonic_fp_booleans-3.0.2/docs/conf_custom.py +0 -68
- pythonic_fp_booleans-3.0.2/docs/conf_devel.py +0 -68
- pythonic_fp_booleans-3.0.2/docs/conf_release.py +0 -68
- pythonic_fp_booleans-3.0.2/docs/index_custom.rst +0 -30
- pythonic_fp_booleans-3.0.2/docs/index_devel.rst +0 -30
- pythonic_fp_booleans-3.0.2/docs/index_release.rst +0 -30
- pythonic_fp_booleans-3.0.2/docs/requirements.txt +0 -2
- pythonic_fp_booleans-3.0.2/docs/source/changelog.rst +0 -7
- pythonic_fp_booleans-3.0.2/docs/source/description.rst +0 -7
- pythonic_fp_booleans-3.0.2/docs/source/docs/flavored.rst +0 -4
- pythonic_fp_booleans-3.0.2/docs/source/docs/index.rst +0 -15
- pythonic_fp_booleans-3.0.2/docs/source/docs/subtypable.rst +0 -8
- pythonic_fp_booleans-3.0.2/docs/source/docs/truthy_falsy.rst +0 -4
- pythonic_fp_booleans-3.0.2/docs/source/releases.rst +0 -24
- pythonic_fp_booleans-3.0.2/docs/source/usage.rst +0 -63
- pythonic_fp_booleans-3.0.2/src/pythonic_fp/booleans/__init__.pyi +0 -0
- pythonic_fp_booleans-3.0.2/src/pythonic_fp/booleans/flavored.py +0 -140
- pythonic_fp_booleans-3.0.2/src/pythonic_fp/booleans/flavored.pyi +0 -19
- pythonic_fp_booleans-3.0.2/src/pythonic_fp/booleans/subtypable.py +0 -166
- pythonic_fp_booleans-3.0.2/src/pythonic_fp/booleans/subtypable.pyi +0 -18
- pythonic_fp_booleans-3.0.2/src/pythonic_fp/booleans/truthy_falsy.py +0 -168
- pythonic_fp_booleans-3.0.2/src/pythonic_fp/booleans/truthy_falsy.pyi +0 -21
- pythonic_fp_booleans-3.0.2/tests/test_bool_vs_fbool.py +0 -195
- pythonic_fp_booleans-3.0.2/tests/test_bool_vs_sbool.py +0 -160
- pythonic_fp_booleans-3.0.2/tests/test_flavored.py +0 -167
- pythonic_fp_booleans-3.0.2/tests/test_flavored_non_shortcut_logic.py +0 -112
- pythonic_fp_booleans-3.0.2/tests/test_flavored_sbool.py +0 -162
- pythonic_fp_booleans-3.0.2/tests/test_subtypable_non_shortcut_logic.py +0 -83
- pythonic_fp_booleans-3.0.2/tests/test_true_false.py +0 -189
- pythonic_fp_booleans-3.0.2/tests/test_true_false_non_shortcut_logic.py +0 -157
- pythonic_fp_booleans-3.0.2/tests/test_typing.py +0 -98
- pythonic_fp_booleans-3.0.2/tests/test_variance.py +0 -187
- {pythonic_fp_booleans-3.0.2 → pythonic_fp_booleans-4.1.0}/LICENSE +0 -0
- {pythonic_fp_booleans-3.0.2 → pythonic_fp_booleans-4.1.0}/src/pythonic_fp/booleans/py.typed +0 -0
|
@@ -1,53 +1,48 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: pythonic-fp-booleans
|
|
3
|
-
Version:
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 4.1.0
|
|
4
|
+
Summary: True Booleans which are subtypable.
|
|
5
5
|
Keywords: booleans,subtypable
|
|
6
6
|
Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
|
|
7
7
|
Requires-Python: >=3.13
|
|
8
|
-
Description-Content-Type: text/
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
9
|
Classifier: Development Status :: 4 - Beta
|
|
10
10
|
Classifier: Framework :: Pytest
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
15
|
Classifier: Typing :: Typed
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
Requires-Dist: pythonic-fp-gadgets>=4.0.2
|
|
18
|
-
|
|
19
|
-
Project-URL:
|
|
20
|
-
Project-URL:
|
|
21
|
-
Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/homepage/html/
|
|
18
|
+
Project-URL: Changelog, https://github.com/grscheller/pythonic-fp-booleans/blob/main/CHANGELOG.md
|
|
19
|
+
Project-URL: Documentation, https://grscheller.github.io/pythonic-fp-booleans/release/html/
|
|
20
|
+
Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/
|
|
22
21
|
Project-URL: Source, https://github.com/grscheller/pythonic-fp-booleans
|
|
23
|
-
|
|
22
|
+
Import-Name: pythonic_fp.booleans
|
|
23
|
+
Import-Namespace: pythonic_fp
|
|
24
24
|
|
|
25
|
-
Pythonic FP - Booleans
|
|
26
|
-
======================
|
|
27
|
-
|
|
28
|
-
PyPI project
|
|
29
|
-
`pythonic-fp-booleans
|
|
30
|
-
<https://pypi.org/project/pythonic-fp-booleans>`_.
|
|
25
|
+
# Pythonic FP - Booleans
|
|
31
26
|
|
|
32
27
|
Subtypable Boolean like classes.
|
|
33
28
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
PyPI project
|
|
30
|
+
[pythonic-fp-booleans](https://pypi.org/project/pythonic-fp-booleans).
|
|
37
31
|
Part of the
|
|
38
|
-
|
|
39
|
-
<https://grscheller.github.io/pythonic-fp>`_
|
|
32
|
+
[pythonic-fp](https://grscheller.github.io/pythonic-fp/)
|
|
40
33
|
PyPI projects.
|
|
41
34
|
|
|
42
|
-
Documentation
|
|
43
|
-
|
|
35
|
+
## Documentation
|
|
36
|
+
|
|
37
|
+
Documentation and other links for this project are hosted on
|
|
38
|
+
[GitHub Pages](https://grscheller.github.io/pythonic-fp/projects/booleans.html).
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
While still compatible with Python shortcut logic, these singleton
|
|
41
|
+
classes can be non-shortcut logically composed with Python’s bitwise
|
|
42
|
+
operators. Unlike Python booleans which bitwise compose as integers,
|
|
43
|
+
these compose as true Booleans in a contravariant way.
|
|
48
44
|
|
|
49
|
-
Copyright and License
|
|
50
|
-
---------------------
|
|
45
|
+
## Copyright and License
|
|
51
46
|
|
|
52
47
|
Copyright (c) 2025-2026 Geoffrey R. Scheller. Licensed under the Apache
|
|
53
48
|
License, Version 2.0. See the LICENSE file for details.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Pythonic FP - Booleans
|
|
2
|
+
|
|
3
|
+
Subtypable Boolean like classes.
|
|
4
|
+
|
|
5
|
+
PyPI project
|
|
6
|
+
[pythonic-fp-booleans](https://pypi.org/project/pythonic-fp-booleans).
|
|
7
|
+
Part of the
|
|
8
|
+
[pythonic-fp](https://grscheller.github.io/pythonic-fp/)
|
|
9
|
+
PyPI projects.
|
|
10
|
+
|
|
11
|
+
## Documentation
|
|
12
|
+
|
|
13
|
+
Documentation and other links for this project are hosted on
|
|
14
|
+
[GitHub Pages](https://grscheller.github.io/pythonic-fp/projects/booleans.html).
|
|
15
|
+
|
|
16
|
+
While still compatible with Python shortcut logic, these singleton
|
|
17
|
+
classes can be non-shortcut logically composed with Python’s bitwise
|
|
18
|
+
operators. Unlike Python booleans which bitwise compose as integers,
|
|
19
|
+
these compose as true Booleans in a contravariant way.
|
|
20
|
+
|
|
21
|
+
## Copyright and License
|
|
22
|
+
|
|
23
|
+
Copyright (c) 2025-2026 Geoffrey R. Scheller. Licensed under the Apache
|
|
24
|
+
License, Version 2.0. See the LICENSE file for details.
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["flit_core>=3.12,<4"]
|
|
3
|
+
build-backend = "flit_core.buildapi"
|
|
4
|
+
|
|
1
5
|
[project]
|
|
2
6
|
name = "pythonic-fp-booleans"
|
|
3
|
-
version = "
|
|
4
|
-
readme = "README.
|
|
7
|
+
version = "4.1.0"
|
|
8
|
+
readme = "README.md"
|
|
5
9
|
requires-python = ">=3.13"
|
|
6
|
-
|
|
7
|
-
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "Geoffrey R. Scheller", email = "geoffrey@scheller.com" },
|
|
12
|
+
]
|
|
8
13
|
keywords = [
|
|
9
14
|
"booleans",
|
|
10
15
|
"subtypable",
|
|
@@ -15,32 +20,41 @@ classifiers = [
|
|
|
15
20
|
"Intended Audience :: Developers",
|
|
16
21
|
"License :: OSI Approved :: Apache Software License",
|
|
17
22
|
"Operating System :: OS Independent",
|
|
18
|
-
"Programming Language :: Python :: 3.
|
|
23
|
+
"Programming Language :: Python :: 3.14",
|
|
19
24
|
"Typing :: Typed",
|
|
20
25
|
]
|
|
21
26
|
dependencies = [
|
|
22
27
|
"pythonic-fp-gadgets>=4.0.2",
|
|
23
28
|
]
|
|
24
|
-
|
|
29
|
+
description = "True Booleans which are subtypable."
|
|
25
30
|
|
|
26
31
|
[project.urls]
|
|
27
|
-
Changelog = "https://github.com/grscheller/pythonic-fp-booleans/blob/main/CHANGELOG.
|
|
28
|
-
Documentation =
|
|
29
|
-
Homepage = "https://grscheller.github.io/pythonic-fp/
|
|
32
|
+
Changelog = "https://github.com/grscheller/pythonic-fp-booleans/blob/main/CHANGELOG.md"
|
|
33
|
+
Documentation ="https://grscheller.github.io/pythonic-fp-booleans/release/html/"
|
|
34
|
+
Homepage = "https://grscheller.github.io/pythonic-fp/"
|
|
30
35
|
Source = "https://github.com/grscheller/pythonic-fp-booleans"
|
|
31
36
|
|
|
32
|
-
[
|
|
37
|
+
[dependency-groups]
|
|
38
|
+
docs = [
|
|
39
|
+
"furo>=2025.12.19",
|
|
40
|
+
"sphinx>=9.1",
|
|
41
|
+
]
|
|
42
|
+
pub = [
|
|
43
|
+
"flit",
|
|
44
|
+
]
|
|
33
45
|
test = [
|
|
34
46
|
"pytest>=8.4.1",
|
|
35
47
|
]
|
|
36
48
|
|
|
37
|
-
[build-system]
|
|
38
|
-
requires = ["flit_core>=3.12,<4"]
|
|
39
|
-
build-backend = "flit_core.buildapi"
|
|
40
|
-
|
|
41
49
|
[tool.flit.module]
|
|
42
50
|
name = "pythonic_fp.booleans"
|
|
43
51
|
|
|
52
|
+
[tool.flit.sdist]
|
|
53
|
+
exclude = [
|
|
54
|
+
"docs/",
|
|
55
|
+
"tests/",
|
|
56
|
+
]
|
|
57
|
+
|
|
44
58
|
[tool.mypy]
|
|
45
59
|
explicit_package_bases = true
|
|
46
60
|
local_partial_types = true
|
|
@@ -49,20 +63,16 @@ warn_redundant_casts = true
|
|
|
49
63
|
warn_return_any = true
|
|
50
64
|
warn_unused_configs = true
|
|
51
65
|
|
|
52
|
-
[tool.
|
|
53
|
-
|
|
54
|
-
live-mode = true
|
|
55
|
-
dmypy = false
|
|
56
|
-
strict = true
|
|
57
|
-
report_progress = true
|
|
58
|
-
|
|
59
|
-
[tool.pytest.ini_options]
|
|
60
|
-
addopts = "-ra"
|
|
66
|
+
[tool.pytest]
|
|
67
|
+
addopts = ["-ra"]
|
|
61
68
|
consider_namespace_packages = true
|
|
62
69
|
testpaths = ["tests/"]
|
|
63
70
|
|
|
64
71
|
[tool.ruff]
|
|
65
|
-
target-version = "
|
|
72
|
+
target-version = "py314"
|
|
73
|
+
|
|
74
|
+
[tool.ruff.lint]
|
|
75
|
+
ignore = ["RUF022", "E741"]
|
|
66
76
|
|
|
67
77
|
[tool.ruff.lint.flake8-quotes]
|
|
68
78
|
docstring-quotes = "double"
|
|
@@ -72,7 +82,6 @@ docstring-quotes = "double"
|
|
|
72
82
|
"E731",
|
|
73
83
|
"C901",
|
|
74
84
|
]
|
|
75
|
-
"**/*.py" = ["E741"]
|
|
76
85
|
|
|
77
86
|
[tool.ruff.format]
|
|
78
87
|
quote-style = "single"
|
{pythonic_fp_booleans-3.0.2 → pythonic_fp_booleans-4.1.0}/src/pythonic_fp/booleans/__init__.py
RENAMED
|
@@ -13,28 +13,15 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
"""
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
Booleans
|
|
17
|
+
========
|
|
18
18
|
|
|
19
|
-
..
|
|
19
|
+
.. admonition:: Subtypable/subtyped Boolean like classes
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
int -> bool;
|
|
26
|
-
int -> SBool;
|
|
27
|
-
SBool -> "FBool(h1)";
|
|
28
|
-
SBool -> "FBool(h2)";
|
|
29
|
-
SBool -> "FBool(h3)";
|
|
30
|
-
SBool -> TF_Bool;
|
|
31
|
-
TF_Bool -> T_Bool;
|
|
32
|
-
TF_Bool -> F_Bool;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
While still compatible with Python shortcut logic, ``SBool`` and its
|
|
36
|
-
subclasses can be non-shortcut logically composed with Python's bitwise
|
|
37
|
-
operators.
|
|
21
|
+
- Like Python's builtin ``bool``, ``SBool`` and its subclasses
|
|
22
|
+
are threadsafe singletons.
|
|
23
|
+
- While still compatible with Python shortcut logic, these can be
|
|
24
|
+
non-shortcut logically composed with Python's bitwise operators.
|
|
38
25
|
|
|
39
26
|
"""
|
|
40
27
|
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Copyright 2023-2026 Geoffrey R. Scheller
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from collections.abc import Hashable
|
|
16
|
+
from threading import Lock
|
|
17
|
+
from typing import ClassVar, Self, final
|
|
18
|
+
|
|
19
|
+
from .subtypable import SBool
|
|
20
|
+
|
|
21
|
+
__all__ = ['FBool', 'truthy', 'falsy']
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@final
|
|
25
|
+
class FBool(SBool):
|
|
26
|
+
"""
|
|
27
|
+
.. admonition:: Favored Booleans
|
|
28
|
+
|
|
29
|
+
When different flavors of the truth matter. Each ``FBool`` is
|
|
30
|
+
an ``SBool`` subtype corresponding to a hashable flavor.
|
|
31
|
+
|
|
32
|
+
.. warning::
|
|
33
|
+
|
|
34
|
+
Combining FBool instances of different flavors with
|
|
35
|
+
bitwise operators will result in a runtime ValueError
|
|
36
|
+
exception.
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
_falsy_dict: ClassVar[dict[Hashable, FBool]] = {}
|
|
41
|
+
_falsy_dict_lock: ClassVar[Lock] = Lock()
|
|
42
|
+
|
|
43
|
+
_truthy_dict: ClassVar[dict[Hashable, FBool]] = {}
|
|
44
|
+
_truthy_dict_lock: ClassVar[Lock] = Lock()
|
|
45
|
+
|
|
46
|
+
def __new__(cls, witness: object, flavor: Hashable) -> Self:
|
|
47
|
+
"""
|
|
48
|
+
.. admonition:: new
|
|
49
|
+
|
|
50
|
+
Traditional singleton pattern but with a ClassVar dict
|
|
51
|
+
to store the for truthy or falsy singleton for each
|
|
52
|
+
hashable flavor.
|
|
53
|
+
|
|
54
|
+
:param witness: Determines truthiness of the FBool instance returned.
|
|
55
|
+
:param flavor: The flavor of FBool to created.
|
|
56
|
+
:returns: The truthy or falsy FBool instance of a particular flavor.
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
if witness:
|
|
60
|
+
if flavor not in cls._truthy_dict:
|
|
61
|
+
with cls._truthy_dict_lock:
|
|
62
|
+
if flavor not in cls._truthy_dict:
|
|
63
|
+
cls._truthy_dict[flavor] = super(SBool, cls).__new__(cls, 1)
|
|
64
|
+
return cls._truthy_dict[flavor]
|
|
65
|
+
else:
|
|
66
|
+
if flavor not in cls._falsy_dict:
|
|
67
|
+
with cls._falsy_dict_lock:
|
|
68
|
+
if flavor not in cls._falsy_dict:
|
|
69
|
+
cls._falsy_dict[flavor] = super(SBool, cls).__new__(cls, 0)
|
|
70
|
+
return cls._falsy_dict[flavor]
|
|
71
|
+
|
|
72
|
+
def __init__(self, witness: object, flavor: Hashable) -> None:
|
|
73
|
+
"""
|
|
74
|
+
.. admonition:: initialize
|
|
75
|
+
|
|
76
|
+
Let the flavored boolean know its flavor.
|
|
77
|
+
|
|
78
|
+
:param witness: Determines truthiness of the FBool instance returned.
|
|
79
|
+
:param flavor: The flavor of FBool to created.
|
|
80
|
+
:type flavor: H: Hashable
|
|
81
|
+
:returns: The truthy or falsy FBool instance of a particular flavor.
|
|
82
|
+
:raises ValueError: If different flavors compared with bitwise operators.
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
if not hasattr(self, '_flavor'):
|
|
86
|
+
self._flavor = flavor
|
|
87
|
+
|
|
88
|
+
def __and__(self, other: int) -> int:
|
|
89
|
+
if type(other) is type(self):
|
|
90
|
+
if other.flavor() != self._flavor:
|
|
91
|
+
msg = 'Error: diffent flavored booleans compared with & operator'
|
|
92
|
+
raise ValueError(msg)
|
|
93
|
+
if self and other:
|
|
94
|
+
return FBool(1, self._flavor)
|
|
95
|
+
else:
|
|
96
|
+
return FBool(0, self._flavor)
|
|
97
|
+
return super().__and__(other)
|
|
98
|
+
|
|
99
|
+
def __or__(self, other: int) -> int:
|
|
100
|
+
if type(other) is type(self):
|
|
101
|
+
if other.flavor() != self.flavor():
|
|
102
|
+
msg = 'Error: diffent flavored booleans compared with | operator'
|
|
103
|
+
raise ValueError(msg)
|
|
104
|
+
if self or other:
|
|
105
|
+
return FBool(1, self._flavor)
|
|
106
|
+
else:
|
|
107
|
+
return FBool(0, self._flavor)
|
|
108
|
+
return super().__or__(other)
|
|
109
|
+
|
|
110
|
+
def __xor__(self, other: int) -> int:
|
|
111
|
+
if type(other) is type(self):
|
|
112
|
+
if other.flavor() != self._flavor:
|
|
113
|
+
msg = 'Error: diffent flavored booleans compared with ^ operator'
|
|
114
|
+
raise ValueError(msg)
|
|
115
|
+
if (self or other) and not (self and other):
|
|
116
|
+
return FBool(1, self._flavor)
|
|
117
|
+
else:
|
|
118
|
+
return FBool(0, self._flavor)
|
|
119
|
+
return super().__xor__(other)
|
|
120
|
+
|
|
121
|
+
def __repr__(self) -> str:
|
|
122
|
+
"""
|
|
123
|
+
.. admonition:: repr string
|
|
124
|
+
|
|
125
|
+
Create strings of the form
|
|
126
|
+
|
|
127
|
+
- ``FBool(True, repr_flavor)``
|
|
128
|
+
- ``FBool(False, repr_flavor)``
|
|
129
|
+
|
|
130
|
+
Where ``repr_flavor = repr(self.flavor())``
|
|
131
|
+
|
|
132
|
+
:returns: A String to reproduce the flavored boolean.
|
|
133
|
+
|
|
134
|
+
"""
|
|
135
|
+
if self:
|
|
136
|
+
return f'FBool(True, {self._flavor!r})'
|
|
137
|
+
return f'FBool(False, {self._flavor!r})'
|
|
138
|
+
|
|
139
|
+
def __str__(self) -> str:
|
|
140
|
+
"""
|
|
141
|
+
.. admonition:: user string
|
|
142
|
+
|
|
143
|
+
Create strings of the form
|
|
144
|
+
|
|
145
|
+
- ``FBool(True, str_flavor)``
|
|
146
|
+
- ``FBool(False, str_flavor)``
|
|
147
|
+
|
|
148
|
+
Where ``str_flavor = str(self.flavor())``
|
|
149
|
+
|
|
150
|
+
:returns: A String meaningful to an end user.
|
|
151
|
+
|
|
152
|
+
"""
|
|
153
|
+
if self:
|
|
154
|
+
return f'FBool(True, {self._flavor!s})'
|
|
155
|
+
return f'FBool(False, {self._flavor!s})'
|
|
156
|
+
|
|
157
|
+
def flavor(self) -> Hashable:
|
|
158
|
+
"""
|
|
159
|
+
.. admonition:: flavor
|
|
160
|
+
|
|
161
|
+
Get the flavor of the ``FBool``, a hashable value.
|
|
162
|
+
|
|
163
|
+
:returns: The flavor.
|
|
164
|
+
|
|
165
|
+
"""
|
|
166
|
+
return self._flavor
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def truthy(flavor: Hashable) -> FBool:
|
|
170
|
+
"""
|
|
171
|
+
.. admonition:: function truthy
|
|
172
|
+
|
|
173
|
+
Returns the truthy singleton ``FBool`` of a particular flavor.
|
|
174
|
+
|
|
175
|
+
:param flavor: Hashable value to determine which
|
|
176
|
+
singleton flavor to return.
|
|
177
|
+
:returns: The truthy singleton of a particular flavor.
|
|
178
|
+
|
|
179
|
+
"""
|
|
180
|
+
return FBool(True, flavor)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def falsy(flavor: Hashable) -> FBool:
|
|
184
|
+
"""
|
|
185
|
+
.. admonition:: Function falsy
|
|
186
|
+
|
|
187
|
+
Returns the falsy singleton ``FBool`` of a particular flavor.
|
|
188
|
+
|
|
189
|
+
:param flavor: Hashable value to determine which
|
|
190
|
+
singleton flavor to return.
|
|
191
|
+
:returns: The falsy singleton of a particular flavor.
|
|
192
|
+
|
|
193
|
+
"""
|
|
194
|
+
return FBool(False, flavor)
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Copyright 2023-2025 Geoffrey R. Scheller
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from collections.abc import Hashable
|
|
16
|
+
from threading import Lock
|
|
17
|
+
from typing import ClassVar, Final, Self, cast, overload
|
|
18
|
+
|
|
19
|
+
from pythonic_fp.gadgets import first_common_ancestor as fca
|
|
20
|
+
from pythonic_fp.gadgets.sentinels.novalue import NoValue
|
|
21
|
+
|
|
22
|
+
__all__ = ['SBool', 'TRUTH', 'LIE']
|
|
23
|
+
|
|
24
|
+
no_value = NoValue()
|
|
25
|
+
|
|
26
|
+
class SBool(int):
|
|
27
|
+
"""
|
|
28
|
+
.. admonition:: Subtypable Boolean
|
|
29
|
+
|
|
30
|
+
Like Python's built in ``bool``, class ``SBool`` is a singleton
|
|
31
|
+
subclass of ``int``. Unlike ``bool``, it can be further subclassed.
|
|
32
|
+
|
|
33
|
+
``SBool`` and its subtypes can also do (non-shortcut) Boolean logic
|
|
34
|
+
using Python bitwise operators.
|
|
35
|
+
|
|
36
|
+
+-------------------+--------+----------------+
|
|
37
|
+
| Boolean operation | symbol | dunder |
|
|
38
|
+
+===================+========+================+
|
|
39
|
+
| not | ``~`` | ``__invert__`` |
|
|
40
|
+
+-------------------+--------+----------------+
|
|
41
|
+
| and | ``&`` | ``__and__`` |
|
|
42
|
+
+-------------------+--------+----------------+
|
|
43
|
+
| or | ``|`` | ``__or__`` |
|
|
44
|
+
+-------------------+--------+----------------+
|
|
45
|
+
| xor | ``^`` | ``__xor__`` |
|
|
46
|
+
+-------------------+--------+----------------+
|
|
47
|
+
|
|
48
|
+
While compatible with Python short-cut logic, , the not
|
|
49
|
+
operator unfortunately always returns a bool.
|
|
50
|
+
|
|
51
|
+
.. tip::
|
|
52
|
+
|
|
53
|
+
Use the bitwise ~ operator to return an opposite SBool
|
|
54
|
+
instance or subclass instance.
|
|
55
|
+
|
|
56
|
+
.. note::
|
|
57
|
+
|
|
58
|
+
These operators are contravariant, that is they will return
|
|
59
|
+
the instance of the latest common ancestor of their
|
|
60
|
+
arguments. More specifically, the instance returned will
|
|
61
|
+
have the type of the least upper bound in the inheritance
|
|
62
|
+
graph of the classes of the two arguments.
|
|
63
|
+
|
|
64
|
+
.. warning::
|
|
65
|
+
|
|
66
|
+
The "bitwise" operators can raise TypeError
|
|
67
|
+
exceptions when applied against an SBool and
|
|
68
|
+
objects not descended from int.
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
_falsy: ClassVar[SBool | NoValue] = NoValue()
|
|
73
|
+
_falsy_lock: ClassVar[Lock] = Lock()
|
|
74
|
+
|
|
75
|
+
_truthy: ClassVar[SBool | NoValue] = NoValue()
|
|
76
|
+
_truthy_lock: ClassVar[Lock] = Lock()
|
|
77
|
+
|
|
78
|
+
@overload
|
|
79
|
+
def __new__(cls, witness: object) -> Self: ...
|
|
80
|
+
@overload
|
|
81
|
+
def __new__(
|
|
82
|
+
cls, witness: object, flavor: Hashable | NoValue = no_value
|
|
83
|
+
) -> Self: ...
|
|
84
|
+
|
|
85
|
+
def __new__(
|
|
86
|
+
cls,
|
|
87
|
+
witness: object,
|
|
88
|
+
flavor: Hashable | NoValue = no_value,
|
|
89
|
+
) -> Self:
|
|
90
|
+
"""
|
|
91
|
+
.. admonition:: new
|
|
92
|
+
|
|
93
|
+
:param witness: Determines truthiness of the ``SBool``.
|
|
94
|
+
:param flavor: Ignored
|
|
95
|
+
:returns: The truthy or falsy ``SBool`` class instance.
|
|
96
|
+
|
|
97
|
+
"""
|
|
98
|
+
if witness:
|
|
99
|
+
if cls._truthy is NoValue():
|
|
100
|
+
with cls._truthy_lock:
|
|
101
|
+
if cls._truthy is NoValue():
|
|
102
|
+
cls._truthy = super().__new__(cls, 1)
|
|
103
|
+
return cast(Self, cls._truthy)
|
|
104
|
+
else:
|
|
105
|
+
if cls._falsy is NoValue():
|
|
106
|
+
with cls._falsy_lock:
|
|
107
|
+
if cls._falsy is NoValue():
|
|
108
|
+
cls._falsy = super().__new__(cls, 0)
|
|
109
|
+
return cast(Self, cls._falsy)
|
|
110
|
+
|
|
111
|
+
@overload
|
|
112
|
+
def __init__(self, witness: object) -> None: ...
|
|
113
|
+
@overload
|
|
114
|
+
def __init__(self, witness: object, flavor: Hashable) -> None: ...
|
|
115
|
+
|
|
116
|
+
def __init__(
|
|
117
|
+
self,
|
|
118
|
+
witness: object = False,
|
|
119
|
+
flavor: Hashable | NoValue = no_value,
|
|
120
|
+
) -> None:
|
|
121
|
+
"""
|
|
122
|
+
.. admonition:: init
|
|
123
|
+
|
|
124
|
+
:param witness: Determines the truthiness of the ``SBool``.
|
|
125
|
+
:param flavor: Ignored by ``SBool``, here only to support
|
|
126
|
+
the Liskov Substitution Principle.
|
|
127
|
+
"""
|
|
128
|
+
self._flavor: Hashable | NoValue = NoValue()
|
|
129
|
+
|
|
130
|
+
def __invert__(self) -> int:
|
|
131
|
+
if self:
|
|
132
|
+
return type(self)(False, self._flavor)
|
|
133
|
+
return type(self)(True, self._flavor)
|
|
134
|
+
|
|
135
|
+
def __and__(self, other: int) -> int:
|
|
136
|
+
try:
|
|
137
|
+
base_class = fca(type(self), type(other))
|
|
138
|
+
except TypeError:
|
|
139
|
+
if type(other) is bool:
|
|
140
|
+
base_class = int
|
|
141
|
+
else:
|
|
142
|
+
msg = f"unsupported operand type(s) for &: '{type(self)}' and '{type(other)}'"
|
|
143
|
+
raise TypeError(msg)
|
|
144
|
+
|
|
145
|
+
if issubclass(base_class, SBool):
|
|
146
|
+
if self._flavor == cast(SBool, other)._flavor:
|
|
147
|
+
flavor = self._flavor
|
|
148
|
+
else:
|
|
149
|
+
flavor = NoValue()
|
|
150
|
+
|
|
151
|
+
if self and other:
|
|
152
|
+
return base_class(True, flavor)
|
|
153
|
+
return base_class(False, flavor)
|
|
154
|
+
else:
|
|
155
|
+
return int(self) & int(other)
|
|
156
|
+
|
|
157
|
+
def __or__(self, other: int) -> int:
|
|
158
|
+
try:
|
|
159
|
+
base_class = fca(type(self), type(other))
|
|
160
|
+
except TypeError:
|
|
161
|
+
if type(other) is bool:
|
|
162
|
+
base_class = int
|
|
163
|
+
else:
|
|
164
|
+
msg = f"unsupported operand type(s) for |: '{type(self)}' and '{type(other)}'"
|
|
165
|
+
raise TypeError(msg)
|
|
166
|
+
|
|
167
|
+
if issubclass(base_class, SBool):
|
|
168
|
+
if self._flavor == cast(SBool, other)._flavor:
|
|
169
|
+
flavor = self._flavor
|
|
170
|
+
else:
|
|
171
|
+
flavor = NoValue()
|
|
172
|
+
|
|
173
|
+
if self or other:
|
|
174
|
+
return base_class(True, flavor)
|
|
175
|
+
return base_class(False, flavor)
|
|
176
|
+
else:
|
|
177
|
+
return int(self) | int(other)
|
|
178
|
+
|
|
179
|
+
def __xor__(self, other: int) -> int:
|
|
180
|
+
try:
|
|
181
|
+
base_class = fca(type(self), type(other))
|
|
182
|
+
except TypeError:
|
|
183
|
+
if type(other) is bool:
|
|
184
|
+
base_class = int
|
|
185
|
+
else:
|
|
186
|
+
msg = f"unsupported operand type(s) for ^: '{type(self)}' and '{type(other)}'"
|
|
187
|
+
raise TypeError(msg)
|
|
188
|
+
|
|
189
|
+
if issubclass(base_class, SBool):
|
|
190
|
+
if self._flavor == cast(SBool, other)._flavor:
|
|
191
|
+
flavor = self._flavor
|
|
192
|
+
else:
|
|
193
|
+
flavor = NoValue()
|
|
194
|
+
|
|
195
|
+
if self and not other or other and not self:
|
|
196
|
+
return base_class(True, flavor)
|
|
197
|
+
return base_class(False, flavor)
|
|
198
|
+
else:
|
|
199
|
+
return int(self) ^ int(other)
|
|
200
|
+
|
|
201
|
+
# override in derived classes
|
|
202
|
+
def __repr__(self) -> str:
|
|
203
|
+
"""
|
|
204
|
+
.. admonition:: repr string
|
|
205
|
+
|
|
206
|
+
- 'SBool(True)' if truthy
|
|
207
|
+
- 'SBool(False)' if falsy
|
|
208
|
+
|
|
209
|
+
:returns: A string to reproduce the ``SBool``.
|
|
210
|
+
|
|
211
|
+
"""
|
|
212
|
+
if self:
|
|
213
|
+
return 'SBool(True)'
|
|
214
|
+
return 'SBool(False)'
|
|
215
|
+
|
|
216
|
+
# override in derived classes
|
|
217
|
+
def __str__(self) -> str:
|
|
218
|
+
"""
|
|
219
|
+
.. admonition:: user string
|
|
220
|
+
|
|
221
|
+
- 'TRUTH' if truthy
|
|
222
|
+
- 'LIE' if falsy
|
|
223
|
+
|
|
224
|
+
:returns: A string meaningful to an end user.
|
|
225
|
+
|
|
226
|
+
"""
|
|
227
|
+
if self:
|
|
228
|
+
return 'TRUTH'
|
|
229
|
+
return 'LIE'
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
TRUTH: Final[SBool] = SBool(True)
|
|
233
|
+
"""
|
|
234
|
+
.. admonition:: TRUTH
|
|
235
|
+
|
|
236
|
+
:var TRUTH: The truthy singleton of type ``SBool``.
|
|
237
|
+
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
LIE: Final[SBool] = SBool(False)
|
|
241
|
+
"""
|
|
242
|
+
.. admonition:: LIE
|
|
243
|
+
|
|
244
|
+
:var LIE: The falsy singleton of type SBool.
|
|
245
|
+
|
|
246
|
+
"""
|