Coverage for src/monadc/either/either.py: 67%

1 from typing import TypeVar, Generic, Callable, Union, Any, Optional, cast

2

3 L = TypeVar('L') # Left type (error/failure)

4 R = TypeVar('R') # Right type (success/value)

5 U = TypeVar('U') # Generic result type

6

7 class Either(Generic[L, R]):

8 """

9 Rust and Scala inspired Either monad for representing computations that can succeed or fail.

10

11 Either[L, R] represents a value that can be either:

12 - Left[L]: A failure/error value of type L

13 - Right[R]: A success value of type R

14 """

15

16 _UNSET = object() # Sentinel value to distinguish from None

17

18 def __new__(cls, left: Any = _UNSET, right: Any = _UNSET) -> 'Either[L, R]':

19 """Factory constructor for Either. Prefer explicit Right()/Left() construction."""

20 if cls is not Either:

21 # Direct subclass instantiation (Left, Right)

22 return super().__new__(cls)

23

24 if left is not cls._UNSET and right is not cls._UNSET:

25 raise ValueError("Cannot specify both left and right")

26

27 if left is not cls._UNSET:

28 from .left import Left

29 return Left(left)

30 elif right is not cls._UNSET:

31 from .right import Right

32 return Right(right)

33 else:

34 raise ValueError("Must specify either left or right")

35

36 # ========================================

37 # Rust / Scala Common API

38 # ========================================

39

40 def is_left(self) -> bool:

41 """Returns True if this Either is a Left, False otherwise."""

42 raise NotImplementedError("Use Left or Right, not Either directly")

43

44 def is_right(self) -> bool:

45 """Returns True if this Either is a Right, False otherwise."""

46 raise NotImplementedError("Use Left or Right, not Either directly")

47

48 def left(self) -> 'Any': # Returns Option[L]

49 """Get the left value as Option - Some(value) if Left, Nil() if Right."""

50 raise NotImplementedError("Use Left or Right, not Either directly")

51

52 def right(self) -> 'Any': # Returns Option[R]

53 """Get the right value as Option - Some(value) if Right, Nil() if Left."""

54 raise NotImplementedError("Use Left or Right, not Either directly")

55

56 def map(self, func: Callable[[Union[L, R]], U]) -> U:

57 """

58 Map both Left and Right values to the same type. Note:

59 - Rust: requires the same type for both left and right.

60 - Scala: maps the right value only.

61 - Python(monadc): blindly applies func to left or right, whichever is present.

62 Also see `map_either` below.

63 """

64 raise NotImplementedError("Use Left or Right, not Either directly")

65

66 def map_left(self, func: Callable[[L], U]) -> 'Either[U, R]':

67 """Transform the Left value if present, otherwise return unchanged Right."""

68 raise NotImplementedError("Use Left or Right, not Either directly")

69

70 def map_right(self, func: Callable[[R], U]) -> 'Either[L, U]':

71 """Transform the Right value if present, otherwise return unchanged Left."""

72 raise NotImplementedError("Use Left or Right, not Either directly")

73

74 # ========================================

75 # RUST API

76 # ========================================

77

78 def either(self, left_func: Callable[[L], U], right_func: Callable[[R], U]) -> U:

79 """Apply left_func to Left value or right_func to Right value. Alias for fold."""

80 return self.fold(left_func, right_func)

81

82 def map_either(self, left_func: Callable[[L], U], right_func: Callable[[R], U]) -> U:

83 """Transform both sides to same type using explicit left/right functions (Rust-style)."""

84 raise NotImplementedError("Use Left or Right, not Either directly")

85

86 def unwrap_left(self) -> L:

87 """Get the left value. Raises exception if this is a Right."""

88 raise NotImplementedError("Use Left or Right, not Either directly")

89

90 def unwrap_right(self) -> R:

91 """Get the right value. Raises exception if this is a Left."""

92 raise NotImplementedError("Use Left or Right, not Either directly")

93

94 def expect_left(self, message: str) -> L:

95 """Get the left value. Raises exception with custom message if this is a Right."""

96 raise NotImplementedError("Use Left or Right, not Either directly")

97

98 def expect_right(self, message: str) -> R:

99 """Get the right value. Raises exception with custom message if this is a Left."""

100 raise NotImplementedError("Use Left or Right, not Either directly")

101

102 def left_or(self, other: 'Either[U, R]') -> 'Either[U, R]':

103 """Return self if Left, otherwise return other."""

104 raise NotImplementedError("Use Left or Right, not Either directly")

105

106 def right_or(self, other: 'Either[L, U]') -> 'Either[L, U]':

107 """Return self if Right, otherwise return other."""

108 raise NotImplementedError("Use Left or Right, not Either directly")

109

110 def left_or_else(self, func: Callable[[], 'Either[U, R]']) -> 'Either[U, R]':

111 """Return self if Left, otherwise call func and return result."""

112 raise NotImplementedError("Use Left or Right, not Either directly")

113

114 def right_or_else(self, func: Callable[[], 'Either[L, U]']) -> 'Either[L, U]':

115 """Return self if Right, otherwise call func and return result."""

116 raise NotImplementedError("Use Left or Right, not Either directly")

117

118 def left_and_then(self, func: Callable[[L], 'Either[U, R]']) -> 'Either[U, R]':

119 """Transform Left value to Either if present, otherwise return unchanged Right."""

120 raise NotImplementedError("Use Left or Right, not Either directly")

121

122 def right_and_then(self, func: Callable[[R], 'Either[L, U]']) -> 'Either[L, U]':

123 """Transform Right value to Either if present, otherwise return unchanged Left."""

124 raise NotImplementedError("Use Left or Right, not Either directly")

125

126 def flip(self) -> 'Either[R, L]':

127 """Flip Left and Right types."""

128 raise NotImplementedError("Use Left or Right, not Either directly")

129

130 # ========================================

131 # SCALA API

132 # ========================================

133 # NOTE: These methods favor the right part (success case) following Scala conventions.

134 # They operate on the Right value and ignore/pass through Left values.

135

136 def swap(self) -> 'Either[R, L]':

137 """Swap Left and Right types."""

138 raise NotImplementedError("Use Left or Right, not Either directly")

139

140 def fold(self, if_left: Callable[[L], U], if_right: Callable[[R], U]) -> U:

141 """Apply if_left to Left value or if_right to Right value."""

142 raise NotImplementedError("Use Left or Right, not Either directly")

143

144 def foreach(self, func: Callable[[R], Any]) -> None:

145 """Execute function on Right value, do nothing for Left (Scala-style)."""

146 raise NotImplementedError("Use Left or Right, not Either directly")

147

148 def get(self) -> R:

149 """Get the Right value. Raises exception if this is a Left (Scala-style)."""

150 raise NotImplementedError("Use Left or Right, not Either directly")

151

152 def get_or_else(self, default: Union[R, Callable[[], R]]) -> R:

153 """Get the Right value or return default if this is a Left (Scala-style)."""

154 raise NotImplementedError("Use Left or Right, not Either directly")

155

156 def to_option(self) -> 'Any': # Returns Option[R]

157 """Convert Either to Option, keeping Right value and losing Left information (Scala-style)."""

158 raise NotImplementedError("Use Left or Right, not Either directly")

159

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

161 """Returns True if this is a Right containing the given value (Scala-style)."""

162 raise NotImplementedError("Use Left or Right, not Either directly")

163

164 def exists(self, predicate: Callable[[R], bool]) -> bool:

165 """Returns True if this is a Right and the predicate holds for the Right value (Scala-style)."""

166 raise NotImplementedError("Use Left or Right, not Either directly")

167

168 def or_else(self, other: Union['Either[L, U]', Callable[[], 'Either[L, U]']]) -> 'Either[L, Union[R, U]]':

169 """Return this Either if it's a Right, otherwise return other (Scala-style)."""

170 raise NotImplementedError("Use Left or Right, not Either directly")