Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add if_left_fn and if_right_fn #42

Merged
merged 2 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/pyella/either.py
Original file line number Diff line number Diff line change
Expand Up @@ -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) <if_left>`
"""
return if_left_fn(self, fn)

def if_right(
self,
fallback: TA_co, # type: ignore [misc] # covariant arg ok, b/c function is pure
Expand All @@ -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) <if_right>`
"""
return if_right_fn(self, fn)

def is_left(self) -> bool:
"""
Alias for :py:func:`is_left(self) <is_left>`
Expand Down Expand Up @@ -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] <Right>` 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
Expand All @@ -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`?
Expand Down
152 changes: 152 additions & 0 deletions src/tests/test_either.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading