Loader Calculation Process

In this part, the calculation process of the common loaders will be introduced according to several samples and diagrams.

is_type & to_type

is_type and to_type are 2 visual-similar loader functions, but actually they are different. By showing their difference, some design logic can be shown.

itype = is_type(int)
otype = to_type(int)

In the code above, what will happen when we call these loaders?

  • When itype is called, this is the flow chart of the calculation

architecture/loader/is_type_calculation.puml.svg

The calculation process is to check the type of value x, so the result should be

itype(1)  # 1
itype(0)  # 0
itype(0.1)  # TypeError
itype('+1')  # TypeError
  • When otype is called, this is the flow chart of the calculation

architecture/loader/to_type_calculation.puml.svg

The calculation process is to transform value x to int, so the result should be

otype(1)  # 1
otype(0)  # 0
otype(0.1)  # 0
otype('+1')  # 1

This is the core logic of the loader framework, it is function-liked, whatever you want to do are included in loaders like itype and otype.

or & and & pipe

Another core usage of the loader framework is the or, and and pipe operations, which can form complex load logic by compositing several single loaders.

  • When or is used, the code should like this

l = is_type(int) | is_type(float)

The flow chart of calculation is

architecture/loader/or_calculation.puml.svg

When is_type(int) is ok, the value x will be returned directly as result. If x is not int, but it it a float object, the value x will be returned as well. But when its type is neither int not float, TypeError will be raised. So the result should be

l(1)  # 1
l(1.1)  # 1.1
l('+1')  # TypeError
  • When and is used, the code should like this

l = is_type(int) & interval(0, 10)

The flow chart of calculation is

architecture/loader/and_calculation.puml.svg

When is_type(int) is not ok, TypeError will be directly raised. If the type is correct, it will check whether the value x is from 0 to 10. So the result should be

l(1.1)  # TypeError
l(1)  # 1
l(10)  # 10
l(11)  # ValueError
l(11.1)  # TypeError
  • When pipe is used, the code should like this

l = minus(1) >> multi(2) >> interval(0, 10)

The flow chart of calculation is

architecture/loader/pipe_calculation.puml.svg

So the result should be

l(1)  # 4
l(4)  # 10
l(5)  # ValueError, because (5 + 1) * 2 = 12 > 10

Turn common function to loader

It is easy to define your own calculation. You can just define a function and wrap it by Loader. For example,

def func(x):
    if x % 2 == 1:
        raise ValueError('not even number')
    return x // 2

The function func can be wrapped and used like this

lfunc = Loader(func)
l = power(3) >> lfunc >> interval(0, 10)

The flow chart of l should be

architecture/loader/loader_calculation.puml.svg

So the calculation result should be

l(0)  # 0
l(1)  # ValueError, 1 not even number
l(2)  # 4
l(3)  # ValueError, 27 not even number
l(4)  # ValueError, 32 not in range [0, 10]

Also, Loader can be used as decorator

@Loader
def func(x):
    if x % 2 == 1:
        raise ValueError('not even number')
    return x // 2

l = power(3) >> func >> interval(0, 10)

The loader l has exactly the same logic.

Collection support

Loader provides support for collection (such as list and dict) as well. For example,

  • Collection mapping

l = collection(plus(1) >> multi(2) >> interval(0, 10))

The flow chart is

architecture/loader/collection_calculation.puml.svg

So the calculation result should be

l([1, 2])  # [4, 6]
l([2, 3, 4])  # [6, 8, 10]
l([2, 3, 5])  # CollectionError, 5 has ValueError
l([5, 6, 3])  # CollectionError, 5, 6 has ValueError
  • Dict generation

l = dict_(
    a=item('a') >> plus(1) >> interval(0, 10),
    b=item('b') >> multi(2) >> interval(0, 10),
)

The flow chart is

architecture/loader/dict_calculation.puml.svg

So the calculation result should be

l({'a': 1, 'b': 2})  # {'a': 2, 'b': 4}
l({'a': -1, 'b': 4})  # {'a': 0, 'b': 8}
l({'a': -10, 'b': 6})  # DictError, 'a', 'b' has ValueError

Norm math and function support

Sometimes we need to do some complex math calculation, so the functions like plus, minus, multi`are not so convenient. `norm can be used at this time.

l1 = Loader((norm(keep()) + 1) * 2) >> interval(0, 10)
l2 = minus(1) >> multi(2) >> interval(0, 10)

l1 and l2 are exactly the same.

In the more complex cases, we can also wrap function into norm by normfunc.

def func(a, b, c):
    if a < 0:
        raise ValueError
    return a + b * c

l = func(-norm(item('a')), item('b'), item('c'))

The flow chart of l should be

architecture/loader/normfunc_calculation.puml.svg

So the calculation result should be

l({'a': 1, 'b': 2, 'c': 3})  # ValueError
l({'a': -1, 'b': 2, 'c': 3})  # 7

Also, normfunc can be used as decorator.

@normfunc
def func(a, b, c):
    if a < 0:
        raise ValueError
    return a + b * c

l has exactly the same logic.

ATTENTION: PLEASE clearly differ the usage `Loader` and `norm` when they are both used in order to avoid the misuses.