diff --git a/src/pyella/either.py b/src/pyella/either.py index 8d53aa3..0cef6f9 100644 --- a/src/pyella/either.py +++ b/src/pyella/either.py @@ -78,6 +78,15 @@ def if_left( """ return if_left(self, fallback) + def if_left_fn( + self, + fn: Callable[[TA_co], TB_co], # type: ignore [misc] # covariant arg ok, b/c function is pure + ) -> TB_co: + """ + Alias for :py:func:`if_left_fn(self, fn) ` + """ + return if_left_fn(self, fn) + def if_right( self, fallback: TA_co, # type: ignore [misc] # covariant arg ok, b/c function is pure @@ -87,6 +96,15 @@ def if_right( """ return if_right(self, fallback) + def if_right_fn( + self, + fn: Callable[[TB_co], TA_co], # type: ignore [misc] # covariant arg ok, b/c function is pure + ) -> TA_co: + """ + Alias for :py:func:`if_right_fn(self, fn) ` + """ + return if_right_fn(self, fn) + def is_left(self) -> bool: """ Alias for :py:func:`is_left(self) ` @@ -286,6 +304,18 @@ def if_left( return fallback if em0.is_left() else cast(TB_co, em0.value) +def if_left_fn( + em0: Either[TA_co, TB_co], + fn: Callable[[TA_co], TB_co], # type: ignore [misc] # covariant arg ok, b/c function is pure +) -> TB_co: + """ + Return the contents of a :py:class:`Right[TB] ` or a mapped left value if it's :py:class:`Left` + + This is a convenience function which doesn't exist in the Haskell implementation + """ + return fn(cast(TA_co, em0.value)) if em0.is_left() else cast(TB_co, em0.value) + + def if_right( em0: Either[TA_co, TB_co], fallback: TA_co, # type: ignore [misc] # covariant arg ok, b/c function is pure @@ -298,6 +328,18 @@ def if_right( return fallback if em0.is_right() else cast(TA_co, em0.value) +def if_right_fn( + em0: Either[TA_co, TB_co], + fn: Callable[[TB_co], TA_co], # type: ignore [misc] # covariant arg ok, b/c function is pure +) -> TA_co: + """ + Return the contents of a :py:class:`Left` or a mapped right value if it's :py:class:`Right` + + This is a convenience function which doesn't exist in the Haskell implementation + """ + return fn(cast(TB_co, em0.value)) if em0.is_right() else cast(TA_co, em0.value) + + def is_left(em0: Either[TA_co, TB_co]) -> bool: """ Is the given :py:class:`Either` a :py:class:`Left`? diff --git a/src/tests/test_either.py b/src/tests/test_either.py index f80a23d..1dd0ae2 100644 --- a/src/tests/test_either.py +++ b/src/tests/test_either.py @@ -276,6 +276,158 @@ def some_function_not_returning_either(value): "Calling `bind` with function that doesn't return Either should throw an ArgumentTypeError", ) + def test_if_left_returns_expected_results(self): + # arrange + some_value = random_str() + some_fallback_value = random_str() + + some_left_value = random_int() + + some_right: Either[int, str] = Either.pure(some_value) + some_left: Either[int, str] = left(some_left_value) + + # act + right_result_from_value = some_right.if_left(some_fallback_value) + left_result_from_value = some_left.if_left(some_fallback_value) + + # assert + self.assertIsInstance( + right_result_from_value, + str, + "Calling `if_left` with fallback value on Right should return unaltered Right value", + ) + self.assertIsInstance( + left_result_from_value, + str, + "Calling `if_left` with fallback value on Left should return fallback value", + ) + + self.assertEqual( + some_value, + right_result_from_value, + "Calling `if_left` on Right should return unaltered Right value", + ) + self.assertEqual( + some_fallback_value, + left_result_from_value, + "Calling `if_left` on Left should return unalterted fallback value", + ) + + def test_if_right_returns_expected_results(self): + # arrange + some_value = random_str() + some_fallback_value = random_int() + + some_left_value = random_int() + + some_right: Either[int, str] = Either.pure(some_value) + some_left: Either[int, str] = left(some_left_value) + + # act + right_result_from_value = some_right.if_right(some_fallback_value) + left_result_from_value = some_left.if_right(some_fallback_value) + + # assert + self.assertIsInstance( + right_result_from_value, + int, + "Calling `if_right` with fallback value on Right should return unaltered Right value", + ) + self.assertIsInstance( + left_result_from_value, + int, + "Calling `if_right` with fallback value on Left should return fallback value", + ) + + self.assertEqual( + some_fallback_value, + right_result_from_value, + "Calling `if_right` on Right should return unalterted fallback value", + ) + self.assertEqual( + some_left_value, + left_result_from_value, + "Calling `if_right` on Left should return unaltered Left value", + ) + + def test_if_left_fn_returns_expected_results(self): + # arrange + some_value = random_str() + + def some_fallback_fn(v: int) -> str: + return str(v) + "!" + + some_left_value = random_int() + + some_right: Either[int, str] = Either.pure(some_value) + some_left: Either[int, str] = left(some_left_value) + + # act + right_result_from_fn = some_right.if_left_fn(some_fallback_fn) + left_result_from_fn = some_left.if_left_fn(some_fallback_fn) + + # assert + self.assertIsInstance( + right_result_from_fn, + str, + "Calling `if_left` with Callable on Left should return unaltered Right value", + ) + self.assertIsInstance( + left_result_from_fn, + str, + "Calling `if_left` with Callable on Left should return fallback value", + ) + + self.assertEqual( + some_value, + right_result_from_fn, + "Calling `if_left` on Right with Callable should return unaltered Right value", + ) + self.assertEqual( + some_fallback_fn(some_left_value), + left_result_from_fn, + "Calling `if_left` on Left with Callable should return mapped Left value", + ) + + def test_if_right_fn_returns_expected_results(self): + # arrange + some_value = random_str() + + def some_fallback_fn(v: str) -> int: + return len(v) + + some_left_value = random_int() + + some_right: Either[int, str] = Either.pure(some_value) + some_left: Either[int, str] = left(some_left_value) + + # act + right_result_from_fn = some_right.if_right_fn(some_fallback_fn) + left_result_from_fn = some_left.if_right_fn(some_fallback_fn) + + # assert + self.assertIsInstance( + right_result_from_fn, + int, + "Calling `if_right` with Callable on Left should return unaltered Right value", + ) + self.assertIsInstance( + left_result_from_fn, + int, + "Calling `if_right` with Callable on Left should return fallback value", + ) + + self.assertEqual( + some_fallback_fn(some_value), + right_result_from_fn, + "Calling `if_right` on Right with Callable should return mapped Left value", + ) + self.assertEqual( + some_left_value, + left_result_from_fn, + "Calling `if_right` on Left with Callable should return unaltered Left value", + ) + def test_rights_and_lefts_return_expected_results(self): # arrange right_values = unique_ints()