Coverage for src/monadc/either/either.py: 67%
76 statements
« prev ^ index » next coverage.py v7.10.2, created at 2025-08-19 20:24 -0700
« prev ^ index » next coverage.py v7.10.2, created at 2025-08-19 20:24 -0700
1from typing import TypeVar, Generic, Callable, Union, Any, Optional, cast
3L = TypeVar('L') # Left type (error/failure)
4R = TypeVar('R') # Right type (success/value)
5U = TypeVar('U') # Generic result type
7class Either(Generic[L, R]):
8 """
9 Rust and Scala inspired Either monad for representing computations that can succeed or fail.
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 """
16 _UNSET = object() # Sentinel value to distinguish from None
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)
24 if left is not cls._UNSET and right is not cls._UNSET:
25 raise ValueError("Cannot specify both left and right")
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")
36 # ========================================
37 # Rust / Scala Common API
38 # ========================================
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")
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")
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")
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")
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")
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")
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")
74 # ========================================
75 # RUST API
76 # ========================================
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)
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")
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")
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")
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")
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")
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")
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")
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")
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")
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")
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")
126 def flip(self) -> 'Either[R, L]':
127 """Flip Left and Right types."""
128 raise NotImplementedError("Use Left or Right, not Either directly")
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.
136 def swap(self) -> 'Either[R, L]':
137 """Swap Left and Right types."""
138 raise NotImplementedError("Use Left or Right, not Either directly")
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")
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")
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")
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")
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")
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")
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")
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")