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