grscheller.untyped.nada
Singleton Representing a Non-existing Value
An attempt to give Python a "bottom" type.
- nada is an attempt to give Python a "bottom" type
While a true bottom type has no instances, nada is a singleton. Python's
evolving typing system seems to reject the concept of a true bottom type.
- types like
Noneand()make for lousy bottoms- they take few methods (much less EVERY method)
Nonehas no length and not indexable,()is at least iterable- returned values must be constantly checked for
- preventing one from blissfully go down the "happy path"
Noneand()are commonly used as sentinel values- hindering both as being interpreted as "nothingness"
The nada object makes for a better bottom like singleton object than
either None and () do.
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 17An attempt to give Python a "bottom" type. 18 19* **nada** is an attempt to give Python a "bottom" type 20 21While a true bottom type has no instances, `nada` is a singleton. Python's 22evolving typing system seems to reject the concept of a true bottom type. 23 24* types like `None` and `()` make for lousy bottoms 25 * they take few methods (much less EVERY method) 26 * `None` has no length and not indexable, `()` is at least iterable 27 * returned values must be constantly checked for 28 * preventing one from blissfully go down the "happy path" 29 * `None` and `()` are commonly used as sentinel values 30 * hindering both as being interpreted as "nothingness" 31 32The `nada` object makes for a better bottom like singleton object than 33either `None` and `()` do. 34""" 35 36from __future__ import annotations 37from typing import Any, Callable, Final, Iterator 38 39__all__ = [ 'Nada', 'nada' ] 40 41class _Sentinel(): 42 def __repr__(self) -> str: 43 return '_Sentinel()' 44 45_sentinel: Final[_Sentinel] = _Sentinel() 46 47class Nada(): 48 """ 49 #### Singleton representing a missing value. 50 51 * singleton nada: nada = Nada() represents a non-existent value 52 * returns itself for arbitrary method calls 53 * returns itself if called as a Callable with arbitrary arguments 54 * interpreted as an empty container by standard Python functions 55 * warning: non-standard equality semantics 56 * comparison compares true only when 2 non-missing values compare true 57 * when compared to itself behaves somewhat like IEEE Float NAN's 58 * `nada is nada` is true 59 * `nada == nada` is false 60 * `nada != nada` is true 61 * thus a == b means two non-missing values compare as equal 62 * warning: does not handle named arguments 63 64 """ 65 __slots__ = () 66 67 def __new__(cls) -> Nada: 68 if not hasattr(cls, 'instance'): 69 cls.instance = super(Nada, cls).__new__(cls) 70 cls._hash = hash((_sentinel, (_sentinel,))) 71 return cls.instance 72 73 def __iter__(self) -> Iterator[Any]: 74 return iter(()) 75 76 def __hash__(self) -> int: 77 return self._hash 78 79 def __repr__(self) -> str: 80 return 'nada' 81 82 def __bool__(self) -> bool: 83 return False 84 85 def __len__(self) -> int: 86 return 0 87 88 def __add__(self, right: Any) -> Nada: 89 return Nada() 90 91 def __radd__(self, left: Any) -> Nada: 92 return Nada() 93 94 def __mul__(self, right: Any) -> Nada: 95 return Nada() 96 97 def __rmul__(self, left: Any) -> Nada: 98 return Nada() 99 100 def __eq__(self, right: Any) -> bool: 101 return False 102 103 def __ne__(self, right: Any) -> bool: 104 return True 105 106 def __ge__(self, right: Any) -> bool: 107 return False 108 109 def __gt__(self, right: Any) -> bool: 110 return False 111 112 def __le__(self, right: Any) -> bool: 113 return False 114 115 def __lt__(self, right: Any) -> bool: 116 return False 117 118 def __getitem__(self, index: int|slice) -> Any: 119 return Nada() 120 121 def __setitem__(self, index: int|slice, item: Any) -> None: 122 return 123 124 def __call__(*args: Any, **kwargs: Any) -> Any: 125 return Nada() 126 127 # def __getattr__(self, name: str) -> Callable[[Any], Any]: 128 # """Comment out for doc generation, pdoc gags on this method.""" 129 # def method(*args: Any, **kwargs: Any) -> Any: 130 # return Nada() 131 # return method 132 133 def nada_get(self, alt: Any=_sentinel) -> Any: 134 """ 135 Get an alternate value, defaults to Nada(). 136 137 """ 138 if alt == _sentinel: 139 return Nada() 140 else: 141 return alt 142 143nada = Nada()
class
Nada:
48class Nada(): 49 """ 50 #### Singleton representing a missing value. 51 52 * singleton nada: nada = Nada() represents a non-existent value 53 * returns itself for arbitrary method calls 54 * returns itself if called as a Callable with arbitrary arguments 55 * interpreted as an empty container by standard Python functions 56 * warning: non-standard equality semantics 57 * comparison compares true only when 2 non-missing values compare true 58 * when compared to itself behaves somewhat like IEEE Float NAN's 59 * `nada is nada` is true 60 * `nada == nada` is false 61 * `nada != nada` is true 62 * thus a == b means two non-missing values compare as equal 63 * warning: does not handle named arguments 64 65 """ 66 __slots__ = () 67 68 def __new__(cls) -> Nada: 69 if not hasattr(cls, 'instance'): 70 cls.instance = super(Nada, cls).__new__(cls) 71 cls._hash = hash((_sentinel, (_sentinel,))) 72 return cls.instance 73 74 def __iter__(self) -> Iterator[Any]: 75 return iter(()) 76 77 def __hash__(self) -> int: 78 return self._hash 79 80 def __repr__(self) -> str: 81 return 'nada' 82 83 def __bool__(self) -> bool: 84 return False 85 86 def __len__(self) -> int: 87 return 0 88 89 def __add__(self, right: Any) -> Nada: 90 return Nada() 91 92 def __radd__(self, left: Any) -> Nada: 93 return Nada() 94 95 def __mul__(self, right: Any) -> Nada: 96 return Nada() 97 98 def __rmul__(self, left: Any) -> Nada: 99 return Nada() 100 101 def __eq__(self, right: Any) -> bool: 102 return False 103 104 def __ne__(self, right: Any) -> bool: 105 return True 106 107 def __ge__(self, right: Any) -> bool: 108 return False 109 110 def __gt__(self, right: Any) -> bool: 111 return False 112 113 def __le__(self, right: Any) -> bool: 114 return False 115 116 def __lt__(self, right: Any) -> bool: 117 return False 118 119 def __getitem__(self, index: int|slice) -> Any: 120 return Nada() 121 122 def __setitem__(self, index: int|slice, item: Any) -> None: 123 return 124 125 def __call__(*args: Any, **kwargs: Any) -> Any: 126 return Nada() 127 128 # def __getattr__(self, name: str) -> Callable[[Any], Any]: 129 # """Comment out for doc generation, pdoc gags on this method.""" 130 # def method(*args: Any, **kwargs: Any) -> Any: 131 # return Nada() 132 # return method 133 134 def nada_get(self, alt: Any=_sentinel) -> Any: 135 """ 136 Get an alternate value, defaults to Nada(). 137 138 """ 139 if alt == _sentinel: 140 return Nada() 141 else: 142 return alt
Singleton representing a missing value.
- singleton nada: nada = Nada() 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
- warning: non-standard equality semantics
- comparison compares true only when 2 non-missing values compare true
- when compared to itself behaves somewhat like IEEE Float NAN's
nada is nadais truenada == nadais falsenada != nadais true
- thus a == b means two non-missing values compare as equal
- comparison compares true only when 2 non-missing values compare true
- warning: does not handle named arguments
nada =
nada