Coverage for src / pyfplib / either.py: 42%

55 statements  

« prev     ^ index     » next       coverage.py v7.13.2, created at 2026-02-04 23:48 +0300

1"""This module provides Either type and 

2two constructors of Either: Left[L, R] and Right[L, R]. 

3""" 

4 

5from typing import Callable, Generic, Optional, TypeVar, Union, cast 

6 

7from pyfplib.option import Option 

8from pyfplib.result import Result, T 

9 

10L = TypeVar("L") 

11R = TypeVar("R") 

12U = TypeVar("U") 

13 

14 

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

16 """Either is a basic class.""" 

17 

18 def __init__(self, *, left: Optional[L] = None, right: Optional[R] = None): 

19 self.__left: Optional[L] = left 

20 self.__right: Optional[R] = right 

21 

22 def is_left(self) -> bool: 

23 """Returns True if left value is not None.""" 

24 return self.__left is not None 

25 

26 def is_right(self) -> bool: 

27 """Returns True if right value is not None.""" 

28 return self.__right is not None 

29 

30 def if_lef(self, fn: Callable[[L], None]) -> "Either[L, R]": 

31 """\ 

32 Calls fn function and returns itself. 

33 The fn function will be called if the left value is not None. 

34 """ 

35 if self.is_left(): 

36 fn(cast(L, self.__left)) 

37 return self 

38 

39 def if_right(self, fn: Callable[[R], None]) -> "Either[L, R]": 

40 """\ 

41 Calls fn function and returns itself. 

42 The fn function will be called if the right value is not None. 

43 """ 

44 if self.is_right(): 

45 fn(cast(R, self.__right)) 

46 return self 

47 

48 def left(self) -> Option[L]: 

49 """Returns left value as Option[L].""" 

50 return Option.from_optional(self.__left) 

51 

52 def right(self) -> Option[R]: 

53 """Returns right value as Option[R].""" 

54 return Option.from_optional(self.__right) 

55 

56 def map_left(self, fn: Callable[[L], U]) -> "Either[U, R]": 

57 """Maps an Either[L, R] to Either[U, R].""" 

58 either: Either[U, R] 

59 if self.is_left(): 

60 either = Left[U, R](fn(cast(L, self.__left))) 

61 else: 

62 either = Right[U, R](cast(R, self.__right)) 

63 return either 

64 

65 def map_right(self, fn: Callable[[R], U]) -> "Either[L, U]": 

66 """Maps an Either[L, R] to Either[L, U].""" 

67 either: Either[L, U] 

68 if self.is_right(): 

69 either = Right[L, U](fn(cast(R, self.__right))) 

70 else: 

71 either = Left[L, U](cast(L, self.__left)) 

72 return either 

73 

74 @staticmethod 

75 def from_result(result: Result) -> "Either[T, Exception]": 

76 """Creates Either[T, Exception] from Result.""" 

77 either: Either 

78 if result.is_ok(): 

79 either = Left[T, Exception](cast(T, result.ok().unwrap())) 

80 else: 

81 either = Right[T, Exception](cast(Exception, result.err().unwrap())) 

82 return either 

83 

84 

85class Left(Either[L, R]): 

86 """\ 

87 This Either constructor creates a new instance of Either 

88 with a left value. 

89 """ 

90 

91 def __init__(self, value: L): 

92 super().__init__(left=value) 

93 

94 @property 

95 def value(self) -> Union[L, R]: 

96 """Getter returns left or right value.""" 

97 return cast(L, self.left()) 

98 

99 

100class Right(Either[L, R]): 

101 """\ 

102 This Either constructor creates a new instance of Either 

103 with a right value. 

104 """ 

105 

106 def __init__(self, value: R): 

107 super().__init__(right=value) 

108 

109 @property 

110 def value(self) -> Union[L, R]: 

111 """Getter returns left or right value.""" 

112 return cast(R, self.right()) 

113 

114 

115__all__ = ("Either", "Left", "Right")