Coverage for src/monadc/option/nil.py: 97%

119 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2025-08-19 20:24 -0700

1from typing import Optional, Callable, Union, Any, Iterator, cast 

2from .option import Option, T, U, V, E 

3 

4 

5class NilType(Option[T]): 

6 

7 __match_args__ = () 

8 

9 _instance: Optional['NilType[Any]'] = None 

10 

11 def __new__(cls) -> 'NilType[Any]': 

12 if cls._instance is None: 

13 cls._instance = cast('NilType[Any]', super().__new__(cls)) 

14 return cls._instance 

15 

16 # ======================================== 

17 # Rust / Scala Common API 

18 # ======================================== 

19 

20 def is_defined(self) -> bool: 

21 return False 

22 

23 def is_empty(self) -> bool: 

24 return True 

25 

26 def map(self, func: Callable[[T], U]) -> Option[U]: 

27 # Nil maps to Nil regardless of function 

28 return Nil() 

29 

30 def flatten(self) -> Option[Any]: 

31 """Flatten Nil to Nil.""" 

32 return cast(Option[Any], self) 

33 

34 def transpose(self) -> Any: # Returns Result[Option[T], E] 

35 """Rust-style: Transpose Nil to Ok(Nil).""" 

36 from ..result import Ok 

37 return Ok(self) 

38 

39 def zip(self, other: Option[U]) -> Option[tuple[T, U]]: 

40 """Rust-style: Return Nil since self is Nil.""" 

41 return cast(Option[tuple[T, U]], self) 

42 

43 def unzip(self) -> tuple[Option[Any], Option[Any]]: 

44 """Unzip Nil to tuple of Nils.""" 

45 return cast(Option[Any], self), cast(Option[Any], self) 

46 

47 def filter(self, predicate: Callable[[T], bool]) -> Option[T]: 

48 # Nil filters to Nil regardless of predicate 

49 return self 

50 

51 # ======================================== 

52 # RUST API 

53 # ======================================== 

54 

55 def is_some(self) -> bool: 

56 """Rust-style: Return False since this is Nil.""" 

57 return False 

58 

59 def is_none(self) -> bool: 

60 """Rust-style: Return True since this is Nil.""" 

61 return True 

62 

63 def unwrap(self) -> T: 

64 """Rust-style: Panic since this is Nil.""" 

65 raise ValueError("Called unwrap() on a Nil value") 

66 

67 def unwrap_or(self, default: T) -> T: 

68 """Rust-style: Return default since this is Nil.""" 

69 return default 

70 

71 def unwrap_or_else(self, func: Callable[[], T]) -> T: 

72 """Rust-style: Call func to compute default since this is Nil.""" 

73 return func() 

74 

75 def expect(self, message: str) -> T: 

76 """Rust-style: Panic with custom message for Nil.""" 

77 raise ValueError(message) 

78 

79 def and_then(self, func: Callable[[T], Option[U]]) -> Option[U]: 

80 """Rust-style: Return Nil since this is Nil.""" 

81 return cast(Option[U], self) 

82 

83 def or_else_with(self, func: Callable[[], Option[T]]) -> Option[T]: 

84 """Rust-style: Call func to get alternative since self is Nil.""" 

85 return func() 

86 

87 def and_(self, other: Option[U]) -> Option[U]: 

88 """Rust-style: Return Nil since self is Nil.""" 

89 return cast(Option[U], self) 

90 

91 def or_(self, other: Option[T]) -> Option[T]: 

92 """Rust-style: Return other since self is Nil.""" 

93 return other 

94 

95 def xor(self, other: Option[T]) -> Option[T]: 

96 """Rust-style: Return other since self is Nil.""" 

97 return other 

98 

99 

100 def zip_with(self, other: Option[U], func: Callable[[T, U], V]) -> Option[V]: 

101 """Rust-style: Return Nil since self is Nil.""" 

102 return cast(Option[V], self) 

103 

104 def inspect(self, func: Callable[[T], Any]) -> Option[T]: 

105 """Rust-style: Do nothing since self is Nil, return self.""" 

106 return self 

107 

108 def get_or_insert(self, value: T) -> T: 

109 """Rust-style: Return the inserted value since Nil is empty.""" 

110 return value 

111 

112 def get_or_insert_with(self, func: Callable[[], T]) -> T: 

113 """Rust-style: Call func and return result since Nil is empty.""" 

114 return func() 

115 

116 def insert(self, value: T) -> T: 

117 """Rust-style: Return the inserted value.""" 

118 return value 

119 

120 def map_or(self, default: U, func: Callable[[T], U]) -> U: 

121 """Rust-style: Return default since Nil has no value.""" 

122 return default 

123 

124 def map_or_else(self, default_func: Callable[[], U], func: Callable[[T], U]) -> U: 

125 """Rust-style: Call default_func since Nil has no value.""" 

126 return default_func() 

127 

128 def ok_or(self, error: E) -> Any: # Returns Result[T, E] 

129 """Rust-style: Convert Nil to Err(error).""" 

130 from ..result import Err 

131 return Err(error) 

132 

133 def ok_or_else(self, func: Callable[[], E]) -> Any: # Returns Result[T, E] 

134 """Rust-style: Convert Nil to Err(func()).""" 

135 from ..result import Err 

136 return Err(func()) 

137 

138 # ======================================== 

139 # SCALA API 

140 # ======================================== 

141 

142 def get(self) -> T: 

143 raise ValueError("Cannot get value from empty Option") 

144 

145 def get_or_else(self, default: Union[T, Callable[[], T]]) -> T: 

146 if callable(default): 

147 try: 

148 return default() 

149 except Exception: 

150 raise ValueError("Default function failed and Option is empty") 

151 return default 

152 

153 def or_else(self, alternative: Union[Option[T], Callable[[], Option[T]]]) -> Option[T]: 

154 if callable(alternative): 

155 try: 

156 return alternative() 

157 except Exception: 

158 return self 

159 return alternative 

160 

161 def filter_not(self, predicate: Callable[[T], bool]) -> Option[T]: 

162 # Nil filters to Nil regardless of predicate 

163 return self 

164 

165 def flat_map(self, func: Callable[[T], Option[U]]) -> Option[U]: 

166 # Nil flat_maps to Nil regardless of function 

167 return Nil() 

168 

169 def fold(self, if_empty: U, func: Callable[[T], U]) -> U: 

170 # Nil always returns the empty value 

171 return if_empty 

172 

173 def foreach(self, func: Callable[[T], Any]) -> None: 

174 """Scala-style: Nil does nothing.""" 

175 pass 

176 

177 def exists(self, predicate: Callable[[T], bool]) -> bool: 

178 # Nil never satisfies any predicate 

179 return False 

180 

181 def forall(self, predicate: Callable[[T], bool]) -> bool: 

182 """Scala-style: Nil vacuously satisfies all predicates.""" 

183 return True 

184 

185 def contains(self, value: Any) -> bool: 

186 """Scala-style: Nil never contains any value.""" 

187 return False 

188 

189 def non_empty(self) -> bool: 

190 """Scala-style: False since Nil is empty.""" 

191 return False 

192 

193 def or_null(self) -> Optional[T]: 

194 """Scala-style: Return None since Nil is empty.""" 

195 return None 

196 

197 def or_none(self) -> Optional[T]: 

198 """Return None since Nil is empty. Alias for or_null.""" 

199 return None 

200 

201 def to_list(self) -> list[T]: 

202 # Nil converts to empty list 

203 return [] 

204 

205 def to_optional(self) -> Optional[T]: 

206 # Nil converts to None 

207 return None 

208 

209 

210 # ======================================== 

211 # PYTHON SPECIAL METHODS 

212 # ======================================== 

213 

214 def __bool__(self) -> bool: 

215 return False 

216 

217 def __iter__(self) -> Iterator[T]: 

218 # Nil yields nothing 

219 return iter([]) 

220 

221 def __eq__(self, other: object) -> bool: 

222 # Nil is equal to other Nil instances 

223 return isinstance(other, NilType) 

224 

225 def __repr__(self) -> str: 

226 return "Nil()" 

227 

228 def __str__(self) -> str: 

229 return self.__repr__() 

230 

231 

232# Export singleton class 

233Nil = NilType