Skip to content

Commit

Permalink
Adding type checks for function args to flat_map, map, Try.of, etc.
Browse files Browse the repository at this point in the history
  • Loading branch information
vickumar1981 committed Mar 29, 2020
1 parent 16d4a0e commit d5240d4
Show file tree
Hide file tree
Showing 10 changed files with 73 additions and 7 deletions.
6 changes: 4 additions & 2 deletions pyeffects/Either.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def of(value):
"""
return Right(value)

def flat_map(self, f):
def flat_map(self, func):
"""Flatmaps a function for :class:`Either <Either>`.
:param func: function returning a pyEffects.Either to apply to flat_map.
Expand All @@ -41,8 +41,10 @@ def flat_map(self, f):
>>> Either.of(5).flat_map(lambda v: Right(v * v))
Right(25)
"""
if not hasattr(func, "__call__"):
raise TypeError("Either.flat_map expects a callable")
if self.is_right():
return f(self.value)
return func(self.value)
else:
return self

Expand Down
2 changes: 2 additions & 0 deletions pyeffects/Future.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ def flat_map(self, func):
>>> Future.of(5).flat_map(lambda v: Future.of(v * v))
Future(Success(25))
"""
if not hasattr(func, "__call__"):
raise TypeError("Future.flat_map expects a callable")
return Future(
lambda cb: self.on_complete(
lambda value: cb(value) if value.is_failure() else func(value.value).on_complete(cb)
Expand Down
16 changes: 11 additions & 5 deletions pyeffects/Monad.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,33 @@ def of(x):
def flat_map(self, f):
raise NotImplementedError("flat_map method needs to be implemented")

def map(self, f):
return self.flat_map(lambda x: self.of(f(x)))
def map(self, func):
if not hasattr(func, "__call__"):
raise TypeError("map expects a callable")
return self.flat_map(lambda x: self.of(func(x)))

def get(self):
if self.biased:
return self.value
raise TypeError("get() cannot be called on this class")
raise TypeError("get cannot be called on this class")

def get_or_else(self, v):
if self.biased:
return self.value
else:
return v

def or_else_supply(self, f):
def or_else_supply(self, func):
if not hasattr(func, "__call__"):
raise TypeError("or_else_supply expects a callable")
if self.biased:
return self.value
else:
return f()
return func()

def or_else(self, other):
if not isinstance(other, Monad):
raise TypeError("or_else can only be chained with other Monad classes")
if self.biased:
return self
else:
Expand Down
2 changes: 2 additions & 0 deletions pyeffects/Option.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ def flat_map(self, func):
>>> Some(5).flat_map(lambda v: Some(v * v))
Some(25)
"""
if not hasattr(func, "__call__"):
raise TypeError("Option.flat_map expects a callable")
if self.is_defined():
return func(self.value)
else:
Expand Down
4 changes: 4 additions & 0 deletions pyeffects/Try.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ def of(func):
>>> Try.of(error)
Failure(failed)
"""
if not hasattr(func, "__call__"):
raise TypeError("Try.of expects a callable")
try:
value = func()
return Success(value)
Expand Down Expand Up @@ -62,6 +64,8 @@ def flat_map(self, func):
>>> Success(5).flat_map(lambda v: Success(v * v))
Success(25)
"""
if not hasattr(func, "__call__"):
raise TypeError("Try.flat_map expects a callable")
if self.is_success():
return func(self.value)
else:
Expand Down
9 changes: 9 additions & 0 deletions tests/test_either.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pyeffects.Either import *
from pyeffects.Monad import identity
from pyeffects.Try import Try
from .random_int_generator import random_int


Expand Down Expand Up @@ -39,3 +40,11 @@ def test_either_associativity(self):

def test_left_either_flat_maps_is_left(self):
assert Left(random_int()).flat_map(lambda v: Right(v)).is_left()

def test_either_flat_map_requires_callable(self):
result = Try.of(lambda: Right(random_int()).flat_map(random_int()))
assert result.is_failure() and isinstance(result.error(), TypeError)

def test_either_repr(self):
assert str(Right(random_int())).startswith("Right")
assert str(Left(random_int())).startswith("Left")
8 changes: 8 additions & 0 deletions tests/test_future.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pyeffects.Future import *
from pyeffects.Try import Try
from .random_int_generator import random_int


Expand Down Expand Up @@ -34,3 +35,10 @@ def test_future_associativity(self):

def test_failed_future_flat_maps_to_failure(self):
assert Future.run(self._fail_future).flat_map(lambda v: Future.of(v)).get().is_failure()

def test_future_flat_map_requires_callable(self):
result = Try.of(lambda: Future.of(random_int()).flat_map(random_int()))
assert result.is_failure() and isinstance(result.error(), TypeError)

def test_future_repr(self):
assert str(Future.of(random_int())).startswith("Future")
12 changes: 12 additions & 0 deletions tests/test_monad.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,15 @@ def test_monad_or_else_with_failure(self):
value = random_int()
result = empty.or_else(Some(value))
assert result.get() == value

def test_monad_map_requires_callable(self):
result = Try.of(lambda: Some(5).map(1))
assert result.is_failure() and isinstance(result.error(), TypeError)

def test_monad_or_else_supply_requires_callable(self):
result = Try.of(lambda: Some(5).or_else_supply(1))
assert result.is_failure() and isinstance(result.error(), TypeError)

def test_monad_or_else_requires_other_monad(self):
result = Try.of(lambda: Some(5).or_else(1))
assert result.is_failure() and isinstance(result.error(), TypeError)
9 changes: 9 additions & 0 deletions tests/test_option.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pyeffects.Option import *
from pyeffects.Monad import identity
from pyeffects.Try import Try
from .random_int_generator import random_int


Expand Down Expand Up @@ -32,3 +33,11 @@ def test_option_associativity(self):

def test_empty_option_flat_maps_to_empty(self):
assert empty.flat_map(lambda v: Option.of(v)).is_empty()

def test_option_flat_map_requires_callable(self):
result = Try.of(lambda: Some(random_int()).flat_map(random_int()))
assert result.is_failure() and isinstance(result.error(), TypeError)

def test_option_repr(self):
assert str(Some(random_int())).startswith("Some")
assert str(empty).startswith("Empty")
12 changes: 12 additions & 0 deletions tests/test_try.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,15 @@ def test_try_map_function(self):

def test_failed_try_flat_maps_to_failure(self):
assert Try.of(self._fail_try).flat_map(self._dbl_int).is_failure()

def test_try_of_requires_callable(self):
result = Try.of(lambda: Try.of(random_int()))
assert result.is_failure() and isinstance(result.error(), TypeError)

def test_try_flat_map_requires_callable(self):
result = Try.of(lambda: Success(random_int()).flat_map(random_int()))
assert result.is_failure() and isinstance(result.error(), TypeError)

def test_try_repr(self):
assert str(Success(random_int())).startswith("Success")
assert str(Failure(random_int())).startswith("Failure")

0 comments on commit d5240d4

Please sign in to comment.