Skip to content

Commit

Permalink
Adds complex math expressions support (#3)
Browse files Browse the repository at this point in the history
* Creates `_MathExpression` class

* Adds `WPS204` to flake8's ignore list

* Modifies `_Callable` class to use `_MathExpression` class in the math operations

* Fixes type safety tests

* Adds tests for `_MathExpression` class

* Adds `complex` to `_Number` type alias

* Renames file from `test_complex_expression.py` to `test_complex_math_expression.py`

* Changes the returns typing hints of math dunder functions from `_Callable` class.
Using the return type as `Callable[[_Number], _Number]` we got some mypy errors
while composing complex math expressions!

* Adds math operations tests for `_Callable` class
  • Loading branch information
thepabloaguilar authored Jul 17, 2020
1 parent 932c513 commit 61582df
Show file tree
Hide file tree
Showing 18 changed files with 337 additions and 55 deletions.
177 changes: 126 additions & 51 deletions lambdas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# -*- coding: utf-8 -*-

import operator
from typing import Callable, Mapping, TypeVar
from functools import partial, reduce
from typing import Callable, List, Mapping, TypeVar, Union

from typing_extensions import Protocol

T1 = TypeVar('T1')
T2 = TypeVar('T2')

_Number = Union[int, float, complex]


def _fmap(callback):
"""Convers callback to instance method with two arguments."""
Expand Down Expand Up @@ -45,7 +48,87 @@ class _LambdaDynamicProtocol(Protocol[T1]):
lambdas_generic_field: T1


class _Callable(object):
class _MathExpression(object): # noqa: WPS214
"""
Mathmatical expression callable class.
This class helps us to build an callable with complex mathematical
expression, basically it's the substitute of `x` in a expression.
When we call this class the number passed trought the instance will be
the `x`.
See the example below:
>>> from lambdas import _MathExpression
>>> complex_expression = (10 ** 2) / _MathExpression() * 10
>>> complex_expression(2)
500.0
"""

def __init__(self) -> None:
self._operations: List[Callable[[_Number], _Number]] = []

def __add__(self, other: _Number) -> '_MathExpression':
return self._add_operation(_flip(operator.add), other)

def __sub__(self, other: _Number) -> '_MathExpression':
return self._add_operation(_flip(operator.sub), other)

def __mul__(self, other: _Number) -> '_MathExpression':
return self._add_operation(_flip(operator.mul), other)

def __floordiv__(self, other: _Number) -> '_MathExpression':
return self._add_operation(_flip(operator.floordiv), other)

def __truediv__(self, other: _Number) -> '_MathExpression':
return self._add_operation(_flip(operator.truediv), other)

def __mod__(self, other: _Number) -> '_MathExpression':
return self._add_operation(_flip(operator.mod), other)

def __pow__(self, other: _Number) -> '_MathExpression':
return self._add_operation(_flip(operator.pow), other)

def __radd__(self, other: _Number) -> '_MathExpression':
return self._add_operation(operator.add, other)

def __rsub__(self, other: _Number) -> '_MathExpression':
return self._add_operation(operator.sub, other)

def __rmul__(self, other: _Number) -> '_MathExpression':
return self._add_operation(operator.mul, other)

def __rfloordiv__(self, other: _Number) -> '_MathExpression':
return self._add_operation(operator.floordiv, other)

def __rtruediv__(self, other: _Number) -> '_MathExpression':
return self._add_operation(operator.truediv, other)

def __rmod__(self, other: _Number) -> '_MathExpression':
return self._add_operation(operator.mod, other)

def __rpow__(self, other: _Number) -> '_MathExpression':
return self._add_operation(operator.pow, other)

def __call__(self, number: _Number) -> _Number:
first_operation, *rest_of_the_operations = self._operations
return reduce(
lambda partial_result, operation: operation(partial_result),
rest_of_the_operations,
first_operation(number),
)

def _add_operation(
self,
operation: Callable[[_Number, _Number], _Number],
other: _Number,
) -> '_MathExpression':
self._operations.append(partial(operation, other))
return self


class _Callable(object): # noqa: WPS214
"""
Short lambda implementation.
Expand All @@ -72,21 +155,47 @@ def __getitem__(
) -> Callable[[Mapping[T1, T2]], T2]:
return operator.itemgetter(key)

__add__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.add,
)
__mul__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.mul,
)
__sub__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.sub,
)
__mod__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.mod,
)
__pow__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.pow,
)
def __add__(self, other: _Number) -> _MathExpression:
return _MathExpression() + other

def __sub__(self, other: _Number) -> _MathExpression:
return _MathExpression() - other

def __mul__(self, other: _Number) -> _MathExpression:
return _MathExpression() * other

def __floordiv__(self, other: _Number) -> _MathExpression:
return _MathExpression() // other

def __truediv__(self, other: _Number) -> _MathExpression:
return _MathExpression() / other

def __mod__(self, other: _Number) -> _MathExpression:
return _MathExpression() % other

def __pow__(self, other: _Number) -> _MathExpression:
return _MathExpression() ** other

def __radd__(self, other: _Number) -> _MathExpression:
return other + _MathExpression()

def __rsub__(self, other: _Number) -> _MathExpression:
return other - _MathExpression()

def __rmul__(self, other: _Number) -> _MathExpression:
return other * _MathExpression()

def __rfloordiv__(self, other: _Number) -> _MathExpression:
return other // _MathExpression()

def __rtruediv__(self, other: _Number) -> _MathExpression:
return other / _MathExpression()

def __rmod__(self, other: _Number) -> _MathExpression:
return other % _MathExpression() # noqa: S001

def __rpow__(self, other: _Number) -> _MathExpression:
return other ** _MathExpression()

__and__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.and_,
Expand All @@ -97,17 +206,7 @@ def __getitem__(
__xor__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.xor,
)

__div__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.truediv,
)
__divmod__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(divmod)
__floordiv__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.floordiv,
)
__truediv__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.truediv,
)

__lshift__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
operator.lshift,
Expand Down Expand Up @@ -149,33 +248,9 @@ def __getitem__(
operator.invert,
)

__radd__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.add),
)
__rmul__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.mul),
)
__rsub__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.sub),
)
__rmod__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.mod),
)
__rpow__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.pow),
)
__rdiv__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.truediv),
)
__rdivmod__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(divmod),
)
__rtruediv__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.truediv),
)
__rfloordiv__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.floordiv),
)

__rlshift__: Callable[['_Callable', T1], Callable[[T1], T1]] = _fmap(
_flip(operator.lshift),
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ exclude =
*.egg
temp

ignore = D100, D104, D401, W504, X100, WPS121, RST303, RST304
ignore = D100, D104, D401, W504, X100, WPS121, RST303, RST304, WPS204

per-file-ignores =
# Disable imports in `__init__.py`:
Expand Down
8 changes: 8 additions & 0 deletions tests/test_callable/test_complex_expression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-

from lambdas import _


def test_complex_expression():
"""Ensures that add works correctly."""
assert ((10 ** 5) / (_ % 3) * 9)(5) == 450000.0
13 changes: 13 additions & 0 deletions tests/test_callable/test_floordiv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-

from lambdas import _


def test_floordiv():
"""Ensures that add works correctly."""
assert (_ // 5)(33) == 6


def test_rfloordiv():
"""Ensures that add works correctly."""
assert (_ // 5)(33) == (33 // _)(5)
13 changes: 13 additions & 0 deletions tests/test_callable/test_mod.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-

from lambdas import _


def test_mod():
"""Ensures that add works correctly."""
assert (_ % 35)(140) == 0


def test_rmod():
"""Ensures that add works correctly."""
assert (149 % _)(36) == (_ % 36)(149)
13 changes: 13 additions & 0 deletions tests/test_callable/test_mul.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-

from lambdas import _


def test_mul():
"""Ensures that add works correctly."""
assert (_ * 20)(10) == 200


def test_rmul():
"""Ensures that add works correctly."""
assert (99 * _)(4) == (_ * 99)(4)
13 changes: 13 additions & 0 deletions tests/test_callable/test_pow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-

from lambdas import _


def test_pow():
"""Ensures that add works correctly."""
assert (_ ** 3)(2) == 8


def test_rpow():
"""Ensures that add works correctly."""
assert (3 ** _)(4) == (_ ** 4)(3)
13 changes: 13 additions & 0 deletions tests/test_callable/test_sub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-

from lambdas import _


def test_sub():
"""Ensures that add works correctly."""
assert (_ - 10)(1) + 1 == -8


def test_rsub():
"""Ensures that add works correctly."""
assert (10 - _)(1) + (_ - 10)(1) == 0
13 changes: 13 additions & 0 deletions tests/test_callable/test_truediv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-

from lambdas import _


def test_truediv():
"""Ensures that add works correctly."""
assert (_ / 6)(39) == 6.5


def test_rtruediv():
"""Ensures that add works correctly."""
assert (42 / _)(8) == (_ / 8)(42)
16 changes: 16 additions & 0 deletions tests/test_math_expression/test_add_expression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-

from lambdas import _MathExpression


def test_add():
"""Ensures that add works correctly."""
add = _MathExpression() + 2
assert add(1) + 1 == 4


def test_radd():
"""Ensures that add works correctly."""
add = _MathExpression() + 2
radd = 2 + _MathExpression()
assert add(1) == radd(1)
9 changes: 9 additions & 0 deletions tests/test_math_expression/test_complex_math_expression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-

from lambdas import _MathExpression


def test_complex_expression():
"""Ensures that add works correctly."""
complex_expression = _MathExpression() * 2 + 1
assert complex_expression(1) == 3
16 changes: 16 additions & 0 deletions tests/test_math_expression/test_floordiv_expression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-

from lambdas import _MathExpression


def test_floordiv():
"""Ensures that add works correctly."""
floordiv = _MathExpression() // 2
assert floordiv(5) == 2


def test_rfloordiv():
"""Ensures that add works correctly."""
floordiv = _MathExpression() // 2
rfloordiv = 5 // _MathExpression()
assert floordiv(5) == rfloordiv(2)
16 changes: 16 additions & 0 deletions tests/test_math_expression/test_mod_expression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-

from lambdas import _MathExpression


def test_mod():
"""Ensures that add works correctly."""
mod = _MathExpression() % 3
assert mod(25) == 1


def test_rmod():
"""Ensures that add works correctly."""
mod = _MathExpression() % 3
rmod = 25 % _MathExpression()
assert mod(25) == rmod(3)
Loading

0 comments on commit 61582df

Please sign in to comment.