Awesome
pyMonet
High abstract python library for functional programming. Contains algebraic data structures known (or unknown) from Haskell or Scala. With MIT licence. Docs
Install:
pip install pymonet
Content:
Either
The Either type represents values with two possibilities: B value of type Either<A, B> is either Left<A> or Right. But not both in the same time.
Maybe
Maybe type is the most common way of representing nothingness (or the null type). Maybe is effectively abstract and has two concrete subtypes: Box (also Some) and Nothing.
Box
Boxs are data-types that store values. No restriction is placed on how they store these values, though there may be restrictions on some methods if a Box is also an instance of a sub-class of Box.
Semigroups
In mathematics, a semigroup is an algebraic structure consisting of a set together with an associative binary operation. A semigroup generalizes a monoid in that there might not exist an identity element. It also (originally) generalized a group (a monoid with all inverses) to a type where every element did not have to have an inverse, this the name semigroup.
Lazy
Lazy are data-types that store functions. Stored function will not be called until call of bind method
Task
Task are data-type for handle execution of functions (in lazy way) transform results of this function and handle errors.
Try
The Try control gives us the ability write safe code without focusing on try-catch blocks in the presence of exceptions.
Validation
It that can hold either a success value or a failure value and has methods for accumulating errors.
Utils
Set of functional programming helpers
Either
The Either type represents values with two possibilities: B value of type Either<A, B> is either Left<A> or Right. But not both in the same time. Left represents error value so any maps and bind will NOT be applied on it.
from pymonet.either import Left, Right
from pymonet.utils import identity
def divide(divided, divider):
if divider == 0:
return Left('can not divide by 0')
return Right(divided, divider)
def handle_error(value):
print ('error {}'.format(value))
def handle_success(value):
print ('success {}'.format(value))
(divide(42, 0)
.map(lambda value: value + 1)
.bind(lambda value: Right(value + 1))
.case(error=handle_error, success=handle_success))
# error 42
(divide(42, 1)
.map(identity, lambda value: value + 1)
.bind(lambda value: Right(value + 1))
.case(error=handle_error, success=handle_success))
# success 44
Maybe
Maybe type is the most common way of representing nothingness (or the null type) with making the possibilities of NullPointer issues disappear. Maybe is effectively abstract and has two concrete subtypes: Some (also Box) and None (also Nothing).
from pymonet.Maybe import Maybe
def get_index(item):
if item in [1,2,3]:
return Maybe.just(42)
return Maybe.nothing()
get_index(42).get_or_else(0) # 0
get_index(1).get_or_else(0) # 3
bind and map methods will be applied only when maybe is not empty
from pymonet.Maybe import Maybe
get_index(42)\
.map(lambda value: value + 1)\
.bind(lambda value: Maybe.just(value + 1))\
.get_or_else(0)
# 0
get_index(1)\
.map(lambda value: value + 1)\
.bind(lambda value: Maybe.just(value + 1))\
.get_or_else(0)
# 3
Filter method will be applied on maybe value and return it with or without value, depend on filter result:
from pymonet.Maybe import Maybe
get_index(42)\
.filter(lambda value: value % 2 == 0)\
.get_or_else(0)
# 0
get_index(3)\
.filter(lambda value: value % 2 == 0)\
.get_or_else(0)
# 0
get_index(2)\
.filter(lambda value: value % 2 == 0)\
.get_or_else(0)
# 2
Box
Boxs are data-types that store values. No restriction is placed on how they store these values, though there may be restrictions on some methods if a Box is also an instance of a sub-class of Box.
from pymonet.box import Box
box = Box(42) # Box<42>
(box
.map(lambda value: value + 1) # Box<43>
.map(lambda value: str(value)) # Box<"43">
.map(lambda value: value[::-1]) # Box<"34">
.bind(lambda value: "output = " + value)) # "output = 34"
Semigroups
In mathematics, a semigroup is an algebraic structure consisting of a set together with an associative binary operation. A semigroup generalizes a monoid in that there might not exist an identity element. It also (originally) generalized a group (a monoid with all inverses) to a type where every element did not have to have an inverse, this the name semigroup.
All
from pymonet.semigroups import All
All(True).concat(All(False)) # All<False>
All(True).concat(All(True)) # All<True>
== operator compares value of semigroups
All(True) == All(True) # True
All(True) == All(False) # False
Sum
from pymonet.semigroups import Sum
Sum(42).concat(Sum(1)) # Sum<43>
Sum(42).concat(Sum(1)).concat(Sum(1)) # Sum<44>
Sum(42).concat(Sum(1).concat(Sum(1))) # Sum<44>
Sum(42).bind(lambda value: value) # 42
First
from pymonet.semigroups import First
First('first').concat(First('Second')) # First<"first">
First('first').bind(lambda value: value[::-1]) # "tsrif"
Map
from pymonet.semigroups import Sum, All, First, Map
ingredient1 = Map({'score': Sum(1), 'won': All(True), 'captain': First('captain america')})
ingredient2 = Map({'score': Sum(2), 'won': All(True), 'captain': First('iron man')})
ingredient1.concat(ingredient2) # Map<{'score': Sum(3), 'won': All(True), 'captain': First('captain america')}>
Lazy
Lazy are data-types that store functions. Stored function will not be called until call of bind method
from pymonet.lazy import Lazy
def fn():
print('fn call')
return 42
def mapper(value):
print('mapper side effect of ' + value)
return value + 1
def side_effect(value):
print('side effect of ' + value)
lazy = Lazy(fn)
mapped_lazy = lazy.map(mapper)
mapped_lazy.bind(side_effect)
# fn call
# mapper side effect of 42
# side effect of 42
Lazy instances memoize output of constructor function
lazy = Lazy(fn)
value1 = lazy.get()
# fn call
value2 = lazy.get()
print(value1, value2)
# 42, 42
Task
Task are data-type for handle execution of functions (in lazy way) transform results of this function and handle errors.
from pymonet.task import Task
def resolvable_fn(reject, resolve):
print('resolve side effect')
resolve(42)
def rejectable_fn(reject, resolve):
print('reject side effect')
reject(0)
resolvable_task = Task.of(resolvable_fn)
rejectable_task = Task.of(rejectable_fn)
map method will be applied only on resolvable tasks during calling bind method
resolvable_task.map(lambda value: value + 1) # Task<() -> 43>
rejectable_task.map(lambda value: value + 1) # Task<() -> 0>
bind method will be applied only on resolvable tasks. bind also will call stored function
def mapper(value):
print('mapper side effect ' + value)
return value + 1
resolvable_task.bind(mapper)
# resolve side effect
# mapper side effect 42
rejectable_task.bind(mapper)
# reject side effect
Try
The Try control gives us the ability write safe code without focusing on try-catch blocks in the presence of exceptions.
from pymonet.monad_try import Try
def divide(dividend, divisor):
return dividend / divisor
def success_callback(value):
print('success: {}'.format(value))
def fail_callback(error):
print('error: {}'.format(value))
(Try.of(divide, 42, 2)
.on_success(success_callback)
.on_fail(fail_callback))
# success: 21
(Try.of(divide, 42, 0)
.on_success(success_callback)
.on_fail(fail_callback))
#error: division by zero
map method will be only applied mapper when exception was not thrown
(Try.of(divide, 42, 2)
.map(lambda value: value + 1)
.on_success(success_callback)
.on_fail(fail_callback))
# success: 22
(Try.of(divide, 42, 0)
.on_success(success_callback)
.map(lambda value: value + 1)
.on_fail(fail_callback))
#error: division by zero
get_or_else method returns value when exception was not thrown
Try.of(divide, 42, 2).get_or_else('Holy Grail') # 21
Try.of(divide, 42, 0).get_or_else('Holy Grail') # 'Holy Grail'
get method should return value with or without exception thrown
Try.of(divide, 42, 2).get() # 21
Try.of(divide, 42, 0).get() # ZeroDivisionError<'division by zero'>
Validation
It that can hold either a success value or a failure value and has methods for accumulating errors
from pymonet.validation import Validation
def test_validation_is_fail():
assert Validation.fail(['fail']).is_fail()
def validate_length(value):
if len(value) < 5:
return Validation.fail(['value not long enough'])
return Validation.success()
def validate_uppercase(value):
if value[0].upper() != value[0]:
return Validation.fail(['value not uppercase'])
return Validation.success()
def validate_contains_special_character(value):
if re.match(r'^[a-zA-Z0-9_]*$', value):
return Validation.fail(['value not contains special character'])
return Validation.success()
def validate(value):
return (Validation.success(value)
.ap(validate_length)
.ap(validate_uppercase)
.ap(validate_contains_special_character))
validate('Success$') # Validation['Success$', []]
validate('Success') # Validation['Success$', ['value not uppercase']]
validate('S$') # Validation['Success$', ['value not long enough']]
validate('s$') # Validation['Success$', ['value not long enough', 'value not uppercase']]
validate('s') # Validation['Success$', ['value not long enough', 'value not uppercase', 'value not contains special character']]
Utils
compose
Compose: performs right-to-left function composition.
from pymonet.utils import \
increase,\
compose,\
curried_map as map,\
curried_filter as filter
compose(
list(range(10)),
map(increase),
filter(is_odd)
)
#[1, 3, 5, 7, 9]
pipe
Pipe: performs left-to-right function composition.
from pymonet.utils import increase, pipe
pipe(42, increase, lambda value: value * 2)
#86
cond
Returns a function which encapsulates if/else, if/else, ... logic. cond takes a list of (predicate, transformer) pairs. All of the arguments to fn are applied to each of the predicates in turn until one returns a truthy value, at which point fn returns the result of applying its arguments to the corresponding transformer.
from pymonet.utils import cond
fn = cond([
(lambda arg: arg == 0, lambda: 'first'),
(lambda arg: arg == 1, lambda: 'second'),
(lambda arg: arg == 2, lambda: 'third').
])
fn(1) # second
# lambda arg: arg == 2 will not be call
memoize
Creates a new function that, when invoked, caches the result of calling fn for a given argument set and returns the result. Subsequent calls to the memoized fn with the same argument set will not result in an additional call to fn; instead, the cached result for that set of arguments will be returned.
from pymonet.utils import memoize, eq
def fn(arg):
print('fn flag')
return arg + 1
memoized_fn = memoize(fn)
memoized_fn(42) # 43
# fn flag
memoized_fn(42) # 43
# print to called
memoized_fn(43) # 44
# fn flag