grscheller.untyped.nothing

A nothing is an attempt to give Python a "bottom" type.

  • unlike a true bottom, it can be instantiated as a singleton
  • types like ~T|None and ~T|() act like a poor man's Optional/Maybe Monads
  • both None and () make for lousy bottom types
  • both don't accept many methods, None has no length, at least () is iterable
  • when returning or iterating values, both must constantly be checked for
  • many developers use None and () as sentinel values
  • therefore by design Null & () are store-able in this data structure
 1# Copyright 2023-2024 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"""A nothing is an attempt to give Python a "bottom" type.
16
17* unlike a true bottom, it can be instantiated as a singleton
18* types like ~T|None and ~T|() act like a poor man's Optional/Maybe Monads
19* both None and () make for lousy bottom types
20* both don't accept many methods, None has no length, at least () is iterable
21* when returning or iterating values, both must constantly be checked for
22* many developers use None and () as sentinel values
23* therefore by design Null & () are store-able in this data structure
24
25"""
26
27from __future__ import annotations
28
29__all__ = [ 'Nothing', 'nothing' ]
30__author__ = "Geoffrey R. Scheller"
31__copyright__ = "Copyright (c) 2023-2024 Geoffrey R. Scheller"
32__license__ = "Apache License 2.0"
33
34from typing import Any, Callable, Iterator, Optional
35
36class Nothing():
37    """Singleton semantically represents a missing value.
38
39    * nothing: Nothing = Nothing() is a singleton representing an absent value
40    * returns itself for arbitrary method calls
41    * returns itself if called as a Callable with arbitrary arguments
42    * interpreted as an empty container by standard Python functions
43    * makes for a better "bottom type" than either None or ()
44    """
45    __slots__ = ()
46
47    def __new__(cls) -> Nothing:
48        if not hasattr(cls, 'instance'):
49            cls.instance = super(Nothing, cls).__new__(cls)
50        return cls.instance
51
52    def __iter__(self) -> Iterator[Any]:
53        return iter(())
54
55    def __repr__(self) -> str:
56        return 'nothing'
57
58    def __bool__(self) -> bool:
59        return False
60
61    def __len__(self) -> int:
62        return 0
63
64    def __add__(self, right: Any) -> Any:
65        return Nothing()
66
67    def __radd__(self, left: Any) -> Any:
68        return Nothing()
69
70    def __mul__(self, right: Any) -> Any:
71        return Nothing()
72
73    def __rmul__(self, left: Any) -> Any:
74        return Nothing()
75
76    def __getitem__(self, index: int|slice) -> Any:
77        return Nothing()
78
79    def __setitem__(self, index: int|slice, item: Any) -> None:
80        return
81
82  # def __getattr__(self, name: str) -> Callable[[Any], Any]:
83  #     """pdoc gags on this commented-out code when generating docs"""
84  #     def method(*args: Any, **kwargs: Any) -> Any:
85  #         return Nothing()
86  #     return method
87
88    def __call__(*args: Any, **kwargs: Any) -> Any:
89        return Nothing()
90
91    def get(self, alt: Optional[Any]=(None, (13, 7), None, 42424242)) -> Any:
92        """Returns an alternate value, defaults Nothing()."""
93        if alt == (None, (13, 7), None, 42424242):  # an unlikely sentinel value
94            return Nothing()
95        else:
96            return alt
97
98nothing = Nothing()
class Nothing:
37class Nothing():
38    """Singleton semantically represents a missing value.
39
40    * nothing: Nothing = Nothing() is a singleton representing an absent value
41    * returns itself for arbitrary method calls
42    * returns itself if called as a Callable with arbitrary arguments
43    * interpreted as an empty container by standard Python functions
44    * makes for a better "bottom type" than either None or ()
45    """
46    __slots__ = ()
47
48    def __new__(cls) -> Nothing:
49        if not hasattr(cls, 'instance'):
50            cls.instance = super(Nothing, cls).__new__(cls)
51        return cls.instance
52
53    def __iter__(self) -> Iterator[Any]:
54        return iter(())
55
56    def __repr__(self) -> str:
57        return 'nothing'
58
59    def __bool__(self) -> bool:
60        return False
61
62    def __len__(self) -> int:
63        return 0
64
65    def __add__(self, right: Any) -> Any:
66        return Nothing()
67
68    def __radd__(self, left: Any) -> Any:
69        return Nothing()
70
71    def __mul__(self, right: Any) -> Any:
72        return Nothing()
73
74    def __rmul__(self, left: Any) -> Any:
75        return Nothing()
76
77    def __getitem__(self, index: int|slice) -> Any:
78        return Nothing()
79
80    def __setitem__(self, index: int|slice, item: Any) -> None:
81        return
82
83  # def __getattr__(self, name: str) -> Callable[[Any], Any]:
84  #     """pdoc gags on this commented-out code when generating docs"""
85  #     def method(*args: Any, **kwargs: Any) -> Any:
86  #         return Nothing()
87  #     return method
88
89    def __call__(*args: Any, **kwargs: Any) -> Any:
90        return Nothing()
91
92    def get(self, alt: Optional[Any]=(None, (13, 7), None, 42424242)) -> Any:
93        """Returns an alternate value, defaults Nothing()."""
94        if alt == (None, (13, 7), None, 42424242):  # an unlikely sentinel value
95            return Nothing()
96        else:
97            return alt

Singleton semantically represents a missing value.

  • nothing: Nothing = Nothing() is a singleton representing an absent value
  • returns itself for arbitrary method calls
  • returns itself if called as a Callable with arbitrary arguments
  • interpreted as an empty container by standard Python functions
  • makes for a better "bottom type" than either None or ()
def get(self, alt: Optional[Any] = (None, (13, 7), None, 42424242)) -> Any:
92    def get(self, alt: Optional[Any]=(None, (13, 7), None, 42424242)) -> Any:
93        """Returns an alternate value, defaults Nothing()."""
94        if alt == (None, (13, 7), None, 42424242):  # an unlikely sentinel value
95            return Nothing()
96        else:
97            return alt

Returns an alternate value, defaults Nothing().

instance = nothing
nothing = nothing