grscheller.untyped.nothing

Singleton Representing a Non-existing Value

A version of grscheller.fp.nada geared for use in less strictly typed code.

  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"""#### Singleton Representing a Non-existing Value
 16
 17A version of grscheller.fp.nada geared for use in less strictly
 18typed code.
 19"""
 20
 21from __future__ import annotations
 22from typing import Any, Callable, Iterator, NewType
 23
 24__all__ = [ 'Nothing', 'nothing' ]
 25
 26_S = NewType('_S', tuple[tuple[()], tuple[tuple[()], tuple[tuple[()], None]]])
 27_sentinel: _S = _S(((), ((), ((), None))))
 28
 29class Nothing():
 30    """
 31    #### Singleton semantically represents a missing value.
 32
 33    * singleton nothing: nothing = Nothing() represents a non-existent value
 34    * returns itself for arbitrary method calls
 35    * returns itself if called as a Callable with arbitrary arguments
 36    * interpreted as an empty container by standard Python functions
 37    * comparison ops compare true only when 2 non-missing values compare true
 38      * when compared to itself behaves somewhat like IEEE Float NAN's
 39        * `nothing is nothing` is true
 40        * `nothing == nothing` is false
 41        * `nothing != nothing` is true
 42    """
 43    __slots__ = ()
 44
 45    def __new__(cls) -> Nothing:
 46        if not hasattr(cls, 'instance'):
 47            cls.instance = super(Nothing, cls).__new__(cls)
 48            cls._hash = hash((_sentinel, (_sentinel,)))
 49        return cls.instance
 50
 51    def __iter__(self) -> Iterator:
 52        return iter(())
 53
 54    def __hash__(self) -> int:
 55        return self._hash
 56
 57    def __repr__(self) -> str:
 58        return 'nothing'
 59
 60    def __bool__(self) -> bool:
 61        return False
 62
 63    def __len__(self) -> int:
 64        return 0
 65
 66    def __add__(self, right: Any) -> Nothing:
 67        return Nothing()
 68
 69    def __radd__(self, left: Any) -> Nothing:
 70        return Nothing()
 71
 72    def __mul__(self, right: Any) -> Nothing:
 73        return Nothing()
 74
 75    def __rmul__(self, left: Any) -> Nothing:
 76        return Nothing()
 77
 78    def __eq__(self, right: Any) -> bool:
 79        """Never equals anything, even itself."""
 80        return False
 81
 82    def __ne__(self, right: Any) -> bool:
 83        """Always does not equal anything, even itself."""
 84        return True
 85
 86    def __ge__(self, right: Any) -> bool:
 87        return False
 88
 89    def __gt__(self, right: Any) -> bool:
 90        return False
 91
 92    def __le__(self, right: Any) -> bool:
 93        return False
 94
 95    def __lt__(self, right: Any) -> bool:
 96        return False
 97
 98    def __getitem__(self, index: int|slice) -> Any:
 99        return Nothing()
100
101    def __setitem__(self, index: int|slice, item: Any) -> None:
102        return
103
104    def __call__(*args: Any, **kwargs: Any) -> Any:
105        return Nothing()
106
107  # def __getattr__(self, name: str) -> Callable[[Any], Any]:
108  #     """Comment out for doc generation, pdoc gags on this method."""
109  #     def method(*args: Any, **kwargs: Any) -> Any:
110  #         return Nothing()
111  #     return method
112
113    def get(self, alt: Any=_sentinel) -> Any:
114        """
115        ##### Get an alternate value, defaults to Nada().
116        """
117        if alt == _sentinel:
118            return Nothing()
119        else:
120            return alt
121
122nothing = Nothing()
class Nothing:
 30class Nothing():
 31    """
 32    #### Singleton semantically represents a missing value.
 33
 34    * singleton nothing: nothing = Nothing() represents a non-existent value
 35    * returns itself for arbitrary method calls
 36    * returns itself if called as a Callable with arbitrary arguments
 37    * interpreted as an empty container by standard Python functions
 38    * comparison ops compare true only when 2 non-missing values compare true
 39      * when compared to itself behaves somewhat like IEEE Float NAN's
 40        * `nothing is nothing` is true
 41        * `nothing == nothing` is false
 42        * `nothing != nothing` is true
 43    """
 44    __slots__ = ()
 45
 46    def __new__(cls) -> Nothing:
 47        if not hasattr(cls, 'instance'):
 48            cls.instance = super(Nothing, cls).__new__(cls)
 49            cls._hash = hash((_sentinel, (_sentinel,)))
 50        return cls.instance
 51
 52    def __iter__(self) -> Iterator:
 53        return iter(())
 54
 55    def __hash__(self) -> int:
 56        return self._hash
 57
 58    def __repr__(self) -> str:
 59        return 'nothing'
 60
 61    def __bool__(self) -> bool:
 62        return False
 63
 64    def __len__(self) -> int:
 65        return 0
 66
 67    def __add__(self, right: Any) -> Nothing:
 68        return Nothing()
 69
 70    def __radd__(self, left: Any) -> Nothing:
 71        return Nothing()
 72
 73    def __mul__(self, right: Any) -> Nothing:
 74        return Nothing()
 75
 76    def __rmul__(self, left: Any) -> Nothing:
 77        return Nothing()
 78
 79    def __eq__(self, right: Any) -> bool:
 80        """Never equals anything, even itself."""
 81        return False
 82
 83    def __ne__(self, right: Any) -> bool:
 84        """Always does not equal anything, even itself."""
 85        return True
 86
 87    def __ge__(self, right: Any) -> bool:
 88        return False
 89
 90    def __gt__(self, right: Any) -> bool:
 91        return False
 92
 93    def __le__(self, right: Any) -> bool:
 94        return False
 95
 96    def __lt__(self, right: Any) -> bool:
 97        return False
 98
 99    def __getitem__(self, index: int|slice) -> Any:
100        return Nothing()
101
102    def __setitem__(self, index: int|slice, item: Any) -> None:
103        return
104
105    def __call__(*args: Any, **kwargs: Any) -> Any:
106        return Nothing()
107
108  # def __getattr__(self, name: str) -> Callable[[Any], Any]:
109  #     """Comment out for doc generation, pdoc gags on this method."""
110  #     def method(*args: Any, **kwargs: Any) -> Any:
111  #         return Nothing()
112  #     return method
113
114    def get(self, alt: Any=_sentinel) -> Any:
115        """
116        ##### Get an alternate value, defaults to Nada().
117        """
118        if alt == _sentinel:
119            return Nothing()
120        else:
121            return alt

Singleton semantically represents a missing value.

  • singleton nothing: nothing = Nothing() represents a non-existent 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
  • comparison ops compare true only when 2 non-missing values compare true
    • when compared to itself behaves somewhat like IEEE Float NAN's
      • nothing is nothing is true
      • nothing == nothing is false
      • nothing != nothing is true
def get(self, alt: Any = ((), ((), ((), None)))) -> Any:
114    def get(self, alt: Any=_sentinel) -> Any:
115        """
116        ##### Get an alternate value, defaults to Nada().
117        """
118        if alt == _sentinel:
119            return Nothing()
120        else:
121            return alt
Get an alternate value, defaults to Nada().
instance = nothing
nothing = nothing