f1 , f2 and f3 , which accept a number and return it increased by 1, 2, and 3, respectively. Also, each function generates a message that represents a report on the operation performed. def f1(x): return (x + 1, str(x) + "+1") def f2(x): return (x + 2, str(x) + "+2") def f3(x): return (x + 3, str(x) + "+3") x , in other words, we would like to calculate x+1+2+3 . In addition, we need to get a human-readable explanation of what each function has done. log = "Ops:" res, log1 = f1(x) log += log1 + ";" res, log2 = f2(res) log += log2 + ";" res, log3 = f3(res) log += log3 + ";" print(res, log) res and log affect the readability of the code, making it difficult to follow the main logic of the program.f3(f2(f1(x))) . Unfortunately, the data types returned by f1 and f2 do not correspond to the types of parameters f2 and f3 . But we can add new functions to the chain: def unit(x): return (x, "Ops:") def bind(t, f): res = f(t[0]) return (res[0], t[1] + res[1] + ";") print(bind(bind(bind(unit(x), f1), f2), f3)) x=0 . Here, v1 , v2 and v3 are the values derived from the calls unit and bind .
unit function converts the input parameter x to a tuple of number and string. The bind function calls the function passed to it as a parameter and accumulates the result in the intermediate variable t .bind function. Now, if we have a function f4 , we simply include it in the chain: bind(f4, bind(f3, ... )) def f1(x): return x + 1 def f2(x): return x + 2 def f3(x): return x + 3 x+1+2+3 . We also need to obtain a list of all values obtained as a result of the operation of our functions, that is, x , x+1 , x+1+2 and x+1+2+3 .f3(f2(f1(x))) returns the final result. But in this case, we will lose intermediate values. lst = [x] res = f1(x) lst.append(res) res = f2(res) lst.append(res) res = f3(res) lst.append(res) print(res, lst) f4 , we again have to repeat this code to get the correct list of intermediate values. def unit(x): return (x, [x]) def bind(t, f): res = f(t[0]) return (res, t[1] + [res]) print(bind(bind(bind(unit(x), f1), f2), f3)) x=0 . Again, v1 , v2 and v3 denote the values resulting from the calls unit and bind .
Employee class with two methods: class Employee: def get_boss(self): # Return the employee's boss def get_wage(self): # Compute the wage Employee has a manager (another object of class Employee ) and a salary, which can be accessed through appropriate methods. Both methods can also return None (the employee has no supervisor, salary is unknown).None . print(john.get_boss().get_wage()) None , our program will end with an error. result = None if john is not None and john.get_boss() is not None and john.get_boss().get_wage() is not None: result = john.get_boss().get_wage() print(result) get_boss and get_wage . If these methods are hard enough (for example, accessing a database), our solution is no good. Therefore, change it: result = None if john is not None: boss = john.get_boss() if boss is not None: wage = boss.get_wage() if wage is not None: result = wage print(result) if . Therefore, we will try to use the same trick as in the previous examples. We define two functions: def unit(e): return e def bind(e, f): return None if e is None else f(e) print(bind(bind(unit(john), Employee.get_boss), Employee.get_wage)) unit function: it simply returns the input parameter. But we will leave it so that it will be easier for us to summarize our experience later.Employee.get_boss(john) instead of john.get_boss() .john.get_boss() returns None .
f1 , f2 , … , fn . If their input parameters coincide in type with the results, we can use a simple chain of the form fn(… f2(f1(x)) …) . The following diagram shows a generalized calculation process with intermediate results, denoted as v1 , v2 , … , vn .

f1 we did some initialization. In the first example, we initialized a variable to store the common log, in the second, for a list of intermediate values. After that, we interleaved the function calls with some kind of linking code: we calculated the aggregate values, checked the result to None .unit and bind . This pattern is called a monad . The bind function contains the glue code while the unit performs initialization. This allows you to simplify the final decision to one line: bind(bind( ... bind(bind(unit(x), f1), f2) ... fn-1), fn) 
unit(x) generates an initial value of v1 . Then bind(v1, f1) generates a new intermediate value v2 , which is used in the next call to bind(v2, f2) . This process continues until the final result is obtained. By defining different unit and bind within this template, we can combine various functions into a single chain of calculations. Monad libraries ( for example, PyMonad or OSlash, - approx. Transl. ) Usually contain ready-to-use monads (pairs of functions unit and bind ) to implement certain compositions of functions.unit and bind must be of the same type as the bind input parameters. This type is called monadic . In terms of the above diagram, the type of variables v1 , v2 , … , vn must be a monadic type.bind calls look inelegant. To avoid this, we define another external function: def pipeline(e, *functions): for f in functions: e = bind(e, f) return e bind(bind(bind(bind(unit(x), f1), f2), f3), f4) pipeline(unit(x), f1, f2, f3, f4) Source: https://habr.com/ru/post/445800/
All Articles