-
Notifications
You must be signed in to change notification settings - Fork 1
/
gof-inspired-decorators.html
620 lines (512 loc) · 62.9 KB
/
gof-inspired-decorators.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
<!DOCTYPE html>
<html lang="en" prefix="og: http://ogp.me/ns# fb: https://www.facebook.com/2008/fbml">
<head>
<title>GOF inspired python decorators - Starting Point</title>
<!-- Using the latest rendering mode for IE -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="canonical" href="https://www.nacnez.com/gof-inspired-decorators.html">
<meta name="author" content="Srinivas Narayanan" />
<meta name="keywords" content="design pattens,functional programming,decorators,python" />
<meta name="description" content="Inspired by GOF patterns, implemented with methods and decorators" />
<meta property="og:site_name" content="Starting Point" />
<meta property="og:type" content="article"/>
<meta property="og:title" content="GOF inspired python decorators"/>
<meta property="og:url" content="https://www.nacnez.com/gof-inspired-decorators.html"/>
<meta property="og:description" content="Inspired by GOF patterns, implemented with methods and decorators"/>
<meta property="article:published_time" content="2019-09-29" />
<meta property="article:section" content="Functional Python" />
<meta property="article:tag" content="design pattens" />
<meta property="article:tag" content="functional programming" />
<meta property="article:tag" content="decorators" />
<meta property="article:tag" content="python" />
<meta property="article:author" content="Srinivas Narayanan" />
<!-- Bootstrap -->
<link rel="stylesheet" href="https://www.nacnez.com/theme/css/bootstrap.darkly.min.css" type="text/css"/>
<link href="https://www.nacnez.com/theme/css/font-awesome.min.css" rel="stylesheet">
<link href="https://www.nacnez.com/theme/css/pygments/monokai.css" rel="stylesheet">
<link rel="stylesheet" href="https://www.nacnez.com/theme/css/style.css" type="text/css"/>
<link href="https://www.nacnez.com/css/custom.css" rel="stylesheet">
<link href="https://www.nacnez.com/feeds/all.atom.xml" type="application/atom+xml" rel="alternate"
title="Starting Point ATOM Feed"/>
<link href="https://www.nacnez.com/feeds/functional-python.atom.xml" type="application/atom+xml" rel="alternate"
title="Starting Point Functional Python ATOM Feed"/>
<!-- Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-79BTGL1LJS"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', G-79BTGL1LJS);
</script>
<!-- End Google Analytics Code -->
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-NBHLX3B');</script>
<!-- End Google Tag Manager -->
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<a href="https://www.nacnez.com/" class="navbar-brand">
<img class="img-responsive pull-left gap-right" src="https://www.nacnez.com/images/myLogo.png" width=""/> Starting Point </a>
</div>
<div class="collapse navbar-collapse navbar-ex1-collapse">
<ul class="nav navbar-nav">
</ul>
<ul class="nav navbar-nav navbar-right">
</ul>
</div>
<!-- /.navbar-collapse -->
</div>
</div> <!-- /.navbar -->
<!-- Banner -->
<!-- End Banner -->
<!-- Content Container -->
<div class="container">
<div class="row">
<div class="col-sm-9">
<section id="content">
<article>
<header class="page-header">
<h1>
<a href="https://www.nacnez.com/gof-inspired-decorators.html"
rel="bookmark"
title="Permalink to GOF inspired python decorators">
GOF inspired python decorators
</a>
</h1>
</header>
<div class="entry-content">
<div class="panel">
<div class="panel-body">
<footer class="post-info">
<span class="label label-success">14 min. read</span>
<span class="label label-default">Date</span>
<span class="published">
<i class="fa fa-calendar"></i><time datetime="2019-09-29T00:00:00+05:30"> Sun 29 September 2019</time>
</span>
<span class="label label-default">Tags</span>
<a href="https://www.nacnez.com/tag/design-pattens.html">design pattens</a>
/
<a href="https://www.nacnez.com/tag/functional-programming.html">functional programming</a>
/
<a href="https://www.nacnez.com/tag/decorators.html">decorators</a>
/
<a href="https://www.nacnez.com/tag/python.html">python</a>
</footer><!-- /.post-info --> </div>
</div>
<h2 id="the-back-story">The back story</h2>
<p>As part of day to day development work, I have been trying to apply functional concepts wherever it makes sense. I have long been a OO developer and hence some of the effects stay with you. Especially design patterns. In my opinion, design patterns - primarily <a href="https://en.wikipedia.org/wiki/Design_Patterns">GOF patterns</a>, though referenced in the context of OO design and languages, have some good ideas that can apply elsewhere too. With languages that support functional features, I believe some of these patterns can be applied at a method/function level instead of carrying around the cruft of classes for just doing behavior (OO enthusiasts bear with me - Classes are great in the right places, but in others they are just there because a language forces you to use <a href="http://bit.ly/2mTnlZz">them</a>).</p>
<p>I am also a fan of Python decorators. Decorators are a very cool and powerful feature of python. It is syntactic sugar to create higher order functions - a very important functional paradigm feature. We can combine multiple functions to get work done - composability. You can read a lot about decorators <a href="http://bit.ly/2lmNNKn">here</a> - a highly recommended read. I will referring to it throughout this post.</p>
<p>So given my background and my current love (functional programming and decorators), I have had a chance to get inspired by GOF pattens and use decorators to create some clean code as part of my work. Here I am going to share that attempt with you.</p>
<h2 id="ok-let-us-get-in">Ok, let us get in</h2>
<p>When you look at the general use and applicability of python decorators they feel like a ready-made way to replace the traditional <a href="https://en.wikipedia.org/wiki/Decorator_pattern">Decorator pattern</a> - and for the code lovers you can look <a href="https://sourcemaking.com/design_patterns/decorator/python/1">here</a>. This thought is not knew and you will see it mentioned in <a href="https://yos.io/2013/07/05/decorator-pattern/">other places</a> too. Theoretically, this is not exactly correct (my opinion). A class level decorator is a structural pattern which allows me to decorate multiple behaviors of the object or component. But in practical terms, I have seen that this does not pan out and method level decorators (using python decorators) are more prevalent.</p>
<p>As I played more with python decorators, I realized that there is one more of my favorite patterns that I could implement using decorators. That is the <a href="https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern">Chain or Responsibility</a> pattern (CoR) - <a href="https://sourcemaking.com/design_patterns/chain_of_responsibility/python/1">code sample</a>. CoR is a behavioral pattern and behaviors are generally managed at method level. Hence decorators feel like a great fit for CoR.</p>
<h2 id="enough-talk">Enough talk!</h2>
<p>It is really difficult to explain my thinking with english words. So let us get down to writing some python words/code. I need an example to illustrate what this is.</p>
<h3 id="the-example">The example</h3>
<p>Here is a contrived example (of course) - a calculator. And since I love <a href="https://en.wikipedia.org/wiki/Test-driven_development">TDD</a> and <a href="https://docs.pytest.org/en/latest/">py.test</a> let us start with a test which can drive our code.</p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">pytest</span>
<span class="kn">import</span> <span class="nn">app.natural_number_calc</span> <span class="kn">as</span> <span class="nn">calc</span>
<span class="nd">@pytest.mark.parametrize</span><span class="p">(</span><span class="s2">"operator, arg1, arg2, output"</span><span class="p">,</span> <span class="p">[</span>
<span class="p">(</span><span class="s1">'+'</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">5</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'+'</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">6</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'-'</span><span class="p">,</span><span class="mi">22</span><span class="p">,</span><span class="mi">12</span><span class="p">,</span><span class="mi">10</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'-'</span><span class="p">,</span><span class="mi">24</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">20</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'*'</span><span class="p">,</span><span class="mi">12</span><span class="p">,</span><span class="mi">7</span><span class="p">,</span><span class="mi">84</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'*'</span><span class="p">,</span><span class="mi">11</span><span class="p">,</span><span class="mi">11</span><span class="p">,</span><span class="mi">121</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'/'</span><span class="p">,</span><span class="mi">84</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">14</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'/'</span><span class="p">,</span><span class="mi">11</span><span class="p">,</span><span class="mi">11</span><span class="p">,</span><span class="mi">1</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'^'</span><span class="p">,</span><span class="mi">11</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">121</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'^'</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">64</span><span class="p">),</span>
<span class="p">])</span>
<span class="k">def</span> <span class="nf">test_calculator</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">,</span> <span class="n">output</span><span class="p">):</span>
<span class="k">assert</span> <span class="n">output</span> <span class="o">==</span> <span class="n">calc</span><span class="o">.</span><span class="n">do</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">test_calculator_operator_not_supported</span><span class="p">():</span>
<span class="k">with</span> <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">)</span> <span class="k">as</span> <span class="n">ve</span><span class="p">:</span>
<span class="n">calc</span><span class="o">.</span><span class="n">do</span><span class="p">(</span><span class="s1">'%'</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
<span class="k">assert</span> <span class="s1">'Calc Error - Operator not supported'</span> <span class="ow">in</span> <span class="nb">str</span><span class="p">(</span><span class="n">ve</span><span class="o">.</span><span class="n">value</span><span class="p">)</span>
<span class="nd">@pytest.mark.parametrize</span><span class="p">(</span><span class="s2">"operator, arg1, arg2"</span><span class="p">,</span> <span class="p">[</span>
<span class="p">(</span><span class="s1">'+'</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'+'</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="o">-</span><span class="mi">2</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'-'</span><span class="p">,</span><span class="mi">22</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'-'</span><span class="p">,</span><span class="o">-</span><span class="mi">24</span><span class="p">,</span><span class="mi">4</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'*'</span><span class="p">,</span><span class="mi">12</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'*'</span><span class="p">,</span><span class="mi">11</span><span class="p">,</span><span class="o">-</span><span class="mi">4</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'/'</span><span class="p">,</span><span class="mi">84</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'/'</span><span class="p">,</span><span class="mi">12</span><span class="p">,</span><span class="o">-</span><span class="mi">2</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'^'</span><span class="p">,</span><span class="mi">11</span><span class="p">,</span><span class="o">-</span><span class="mi">2</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'^'</span><span class="p">,</span><span class="o">-</span><span class="mi">4</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
<span class="p">])</span>
<span class="k">def</span> <span class="nf">test_calculator_non_natural_numbers_not_supported</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">with</span> <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">)</span> <span class="k">as</span> <span class="n">ve</span><span class="p">:</span>
<span class="n">calc</span><span class="o">.</span><span class="n">do</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>
<span class="k">assert</span> <span class="s1">'Calc Error - Natural numbers only supported'</span> <span class="ow">in</span> <span class="nb">str</span><span class="p">(</span><span class="n">ve</span><span class="o">.</span><span class="n">value</span><span class="p">)</span>
</pre></div>
<p>The test suite covers everything which I want to implement with my calculator. So we have a start.</p>
<h3 id="that-is-some-c">That is some c***!</h3>
<p>For satisfying the test suite, here is some plain vanilla code.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">do</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">if</span> <span class="n">operator</span> <span class="o">==</span> <span class="s1">'+'</span><span class="p">:</span>
<span class="k">return</span> <span class="n">_add</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">operator</span> <span class="o">==</span> <span class="s1">'-'</span><span class="p">:</span>
<span class="k">return</span> <span class="n">_subtract</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">operator</span> <span class="o">==</span> <span class="s1">'*'</span><span class="p">:</span>
<span class="k">return</span> <span class="n">_multiply</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">operator</span> <span class="o">==</span> <span class="s1">'/'</span><span class="p">:</span>
<span class="k">return</span> <span class="n">_divide</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">operator</span> <span class="o">==</span> <span class="s1">'^'</span><span class="p">:</span>
<span class="k">return</span> <span class="n">_power</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s1">'Calc Error - Operator not supported'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_power</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">if</span> <span class="n">arg1</span> <span class="o"><</span> <span class="mi">1</span> <span class="ow">or</span> <span class="n">arg2</span> <span class="o"><</span> <span class="mi">1</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s1">'Calc Error - Natural numbers only supported'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">**</span> <span class="n">arg2</span>
<span class="k">def</span> <span class="nf">_divide</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">if</span> <span class="n">arg1</span> <span class="o"><</span> <span class="mi">1</span> <span class="ow">or</span> <span class="n">arg2</span> <span class="o"><</span> <span class="mi">1</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s1">'Calc Error - Natural numbers only supported'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">/</span> <span class="n">arg2</span>
<span class="k">def</span> <span class="nf">_multiply</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">if</span> <span class="n">arg1</span> <span class="o"><</span> <span class="mi">1</span> <span class="ow">or</span> <span class="n">arg2</span> <span class="o"><</span> <span class="mi">1</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s1">'Calc Error - Natural numbers only supported'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">*</span> <span class="n">arg2</span>
<span class="k">def</span> <span class="nf">_subtract</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">if</span> <span class="n">arg1</span> <span class="o"><</span> <span class="mi">1</span> <span class="ow">or</span> <span class="n">arg2</span> <span class="o"><</span> <span class="mi">1</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s1">'Calc Error - Natural numbers only supported'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">-</span> <span class="n">arg2</span>
<span class="k">def</span> <span class="nf">_add</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">if</span> <span class="n">arg1</span> <span class="o"><</span> <span class="mi">1</span> <span class="ow">or</span> <span class="n">arg2</span> <span class="o"><</span> <span class="mi">1</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s1">'Calc Error - Natural numbers only supported'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">+</span> <span class="n">arg2</span>
</pre></div>
<p>Yeah, I know. This is pathetic code... so many <code>if</code>s and <code>elif</code>s and all. That is on purpose so that we can improve it with some <em>decorator and pattern</em> goodness. And it is code that works and hence our test passes! We are on <em>green</em> mode & we can start refactoring.</p>
<h3 id="let-us-start-decorating">Let us start decorating</h3>
<p>If you look at the above code we see an obvious copy paste case. The check for natural numbers is repeated on every operation code and we can easily <strong>decorate</strong> each operation code using the Decorator pattern or in our case python decorators. Let us first look at the decorator code.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">only_natural</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">if</span> <span class="n">arg1</span> <span class="o"><</span> <span class="mi">1</span> <span class="ow">or</span> <span class="n">arg2</span> <span class="o"><</span> <span class="mi">1</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s1">'Calc Error - Natural numbers only supported'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">func</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>
<span class="k">return</span> <span class="n">wrapper</span>
</pre></div>
<p>If you know your decorators, this is a <a href="https://realpython.com/primer-on-python-decorators/#simple-decorators">simple decorator</a> in action. It takes the function that needs to be wrapped. It defines an inner wrapper function which take two arguments which checks the arguments are natural numbers. If they are then the wrapped function is called else raise an error. Let us now use this decorator on our calculator module.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">do</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">if</span> <span class="n">operator</span> <span class="o">==</span> <span class="s1">'+'</span><span class="p">:</span>
<span class="k">return</span> <span class="n">_add</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">operator</span> <span class="o">==</span> <span class="s1">'-'</span><span class="p">:</span>
<span class="k">return</span> <span class="n">_subtract</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">operator</span> <span class="o">==</span> <span class="s1">'*'</span><span class="p">:</span>
<span class="k">return</span> <span class="n">_multiply</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">operator</span> <span class="o">==</span> <span class="s1">'/'</span><span class="p">:</span>
<span class="k">return</span> <span class="n">divide</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">operator</span> <span class="o">==</span> <span class="s1">'^'</span><span class="p">:</span>
<span class="k">return</span> <span class="n">_power</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s1">'Calc Error - Operator not supported'</span><span class="p">)</span>
<span class="nd">@only_natural</span> <span class="c1"># decorator applied</span>
<span class="k">def</span> <span class="nf">_power</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">**</span> <span class="n">arg2</span> <span class="c1">#1</span>
<span class="nd">@only_natural</span>
<span class="k">def</span> <span class="nf">divide</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">/</span> <span class="n">arg2</span> <span class="c1">#2</span>
<span class="nd">@only_natural</span>
<span class="k">def</span> <span class="nf">_multiply</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">*</span> <span class="n">arg2</span> <span class="c1">#3</span>
<span class="nd">@only_natural</span>
<span class="k">def</span> <span class="nf">_subtract</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">-</span> <span class="n">arg2</span> <span class="c1">#4</span>
<span class="nd">@only_natural</span>
<span class="k">def</span> <span class="nf">_add</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">+</span> <span class="n">arg2</span> <span class="c1">#5</span>
</pre></div>
<p>You can see that our calculator is surely improved (note the numbers) using the <em>decorator</em> pattern created with decorators. But the <code>do</code> method is still a eyesore. With just 5 operations, we have a huge <code>if/elif/else</code> clause and this will only grow further if we want to support more operations (I understand this is a toy example but you get the picture).</p>
<h3 id="enter-cor">Enter CoR</h3>
<p>So how can we improve this. Let us dig in. It is clear that for each operation, there is a corresponding method to handle it. The handling determination happens one after the other. It almost feels like a set of actions to do... a chain of things to do... a Chain of Responsibilities to complete (come on, that is not such a bad lead up!). Let us give it a shot once.</p>
<p>Before we get to the code let us understand a bit. A classic COR goes roughly like this. A request which needs to be processed is given to the first link in the chain of processors. That processor either processes it if it can or passes it on to the next processor. A given processor knows what it can process and also knows who is the next one in the chain. That is pretty much the essence of it. The class based style is already referred earlier, so let us try to do this with decorators which can work at method level.</p>
<p>First shot:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">cor</span><span class="p">(</span><span class="nb">next</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="n">current</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">inner</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">):</span>
<span class="n">output</span> <span class="o">=</span> <span class="n">current</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
<span class="k">return</span> <span class="n">output</span> <span class="k">if</span> <span class="n">output</span> <span class="k">else</span> <span class="nb">next</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span> <span class="k">if</span> <span class="nb">next</span> <span class="k">else</span> <span class="bp">None</span>
<span class="k">return</span> <span class="n">inner</span>
<span class="k">return</span> <span class="n">wrapper</span>
</pre></div>
<p>This is a more involved decorator than the first one. Because we need to take an argument, we need this double nested structure. The outer most <code>cor</code> function is the visible annotation part of the decorator and it takes the function object that needs to be called next in the chain as argument. The <code>cor</code> function defines a <code>wrapper</code> function which is the one that accepts the actual function that is being decorated - the <code>current</code>. The <code>wrapper</code> in turn has the <code>inner</code> function where the actual work happens. It calls the current function and gets its response. If that response is valid (a simple <em>truthy</em> response), then it means that request has been processed and the chain can be broken to return the result. If the response is not valid, it means that the current function is not the one to handle the request. Hence the control has to pass on to the next processor or function (which is available to <code>inner</code> because it is a <a href="https://en.wikipedia.org/wiki/Closure_(computer_programming)">closure</a> - another functional feature of python. You can read more about decorators that take arguments <a href="https://realpython.com/primer-on-python-decorators/#decorators-with-arguments">here</a>.</p>
<p>For this to <a href="https://realpython.com/primer-on-python-decorators/#nesting-decorators">nest</a> along with the already existing decorator for natural numbers, we need to do some changes to that decorator.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">only_natural_with_operator</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">if</span> <span class="n">arg1</span> <span class="o"><</span> <span class="mi">1</span> <span class="ow">or</span> <span class="n">arg2</span> <span class="o"><</span> <span class="mi">1</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s1">'Calc Error - Natural numbers only supported'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">func</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>
<span class="k">return</span> <span class="n">wrapper</span>
</pre></div>
<p>The above one is just a simple tweak, so let us move on. Let us look at the calculator to figure out how its usage gets manifested.</p>
<h3 id="what-the-cor">What the CoR?</h3>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">do</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">_add</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>
<span class="k">if</span> <span class="n">value</span><span class="p">:</span>
<span class="k">return</span> <span class="n">value</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s1">'Calc Error - Operator not supported'</span><span class="p">)</span>
<span class="nd">@only_natural_with_operator</span>
<span class="k">def</span> <span class="nf">_power</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">if</span> <span class="n">operator</span> <span class="o">==</span> <span class="s1">'^'</span><span class="p">:</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">**</span> <span class="n">arg2</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">None</span>
<span class="nd">@only_natural_with_operator</span>
<span class="nd">@cor</span><span class="p">(</span><span class="nb">next</span><span class="o">=</span><span class="n">_power</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_divide</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">if</span> <span class="n">operator</span> <span class="o">==</span> <span class="s1">'/'</span><span class="p">:</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">/</span> <span class="n">arg2</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">None</span>
<span class="nd">@only_natural_with_operator</span>
<span class="nd">@cor</span><span class="p">(</span><span class="nb">next</span><span class="o">=</span><span class="n">_divide</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_multiply</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">if</span> <span class="n">operator</span> <span class="o">==</span> <span class="s1">'*'</span><span class="p">:</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">*</span> <span class="n">arg2</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">None</span>
<span class="nd">@only_natural_with_operator</span>
<span class="nd">@cor</span><span class="p">(</span><span class="nb">next</span><span class="o">=</span><span class="n">_multiply</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_subtract</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">if</span> <span class="n">operator</span> <span class="o">==</span> <span class="s1">'-'</span><span class="p">:</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">-</span> <span class="n">arg2</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">None</span>
<span class="nd">@only_natural_with_operator</span>
<span class="nd">@cor</span><span class="p">(</span><span class="nb">next</span><span class="o">=</span><span class="n">_subtract</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_add</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">if</span> <span class="n">operator</span> <span class="o">==</span> <span class="s1">'+'</span><span class="p">:</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">+</span> <span class="n">arg2</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">None</span>
</pre></div>
<p>Hmm... Pretty underwhelming to put it nicely. But before we get into it, let me first explain what this is doing.</p>
<p>The <code>do</code> method just calls the first method in the chain, the <code>_add</code> method. It expects that the processing gets completed to return a valid result. If it gets back an invalid result (<code>None</code>), then it understands that this calculation cannot be processed and throws an Error. The <code>do</code> method has improved for sure.</p>
<p>The <code>_add</code> method declares the next operator/function to call as decorator parameter - (<code>@cor(next=_subtract)</code>). In the body it checks if it can process the request - <em>is it the right operator?</em> . If it is then great, else it returns <code>None</code> denoting that it is not interested. This happens in each operator/function till we reach <code>_power</code> which has no next. So the chain stops here. We can of course extend the chain from this point onwards and for that we don't have to touch the <code>do</code> or the <code>_add</code> (or other) methods. All the methods are separated. This <code>cor</code> decorator could be used to create a chain of any set of functions as long as they follow the basic contract of returning a truthy value if they did the processing or a falsy value if they want to pass it on (provided if they have defined a next).</p>
<h3 id="cor-20">CoR 2.0?</h3>
<p>Now back to that bad feeling we get on seeing the resultant code. In the process of introducing CoR to save the <code>do</code> method, the operation functions have lost their charm. They are crowded now and that is not what we want. And when we look closely, we see that each operation function does one common thing: it checks if the operation is what it can handle, before it actually does the real work. The error handling in the <code>do</code> function looks similar in some way too. All these seem to be common behavior which can be applied to these functions using a <em>wrapper/decorator</em> - is it not? Let us get our decorator pattern back now. The improved cor (2.0) decorator looks like this.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">calc_cor</span><span class="p">(</span><span class="n">my_operator</span><span class="p">,</span> <span class="nb">next</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="n">current</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">inner</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">):</span>
<span class="k">if</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="n">my_operator</span><span class="p">:</span>
<span class="k">return</span> <span class="n">current</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
<span class="k">elif</span> <span class="nb">next</span><span class="p">:</span>
<span class="k">return</span> <span class="nb">next</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s1">'Calc Error - Operator not supported'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">inner</span>
<span class="k">return</span> <span class="n">wrapper</span>
</pre></div>
<p>This is no longer the very generic <code>cor</code> decorator we started with but we sort of expected that. This is a decorated-calculator-specific-CoR - <code>calc_cor</code>. This decorator takes two arguments. The first one is the operator supported by the current function and the second one is the next function. It also expects the the <em>current</em> function (which is being decorated) to always takes the operator symbol as the first argument - the implicit contract. Since the operator is passed, the check of applicability can be done in the decorator itself. Also it does the error handling. This is the <em>decorated</em> part of the new CoR. Now let us see how this changes our calculator code.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">do</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">return</span> <span class="n">_add</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>
<span class="nd">@only_natural_with_operator</span>
<span class="nd">@calc_cor</span><span class="p">(</span><span class="n">my_operator</span><span class="o">=</span><span class="s1">'^'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_power</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">**</span> <span class="n">arg2</span>
<span class="nd">@only_natural_with_operator</span>
<span class="nd">@calc_cor</span><span class="p">(</span><span class="n">my_operator</span><span class="o">=</span><span class="s1">'/'</span><span class="p">,</span> <span class="nb">next</span><span class="o">=</span><span class="n">_power</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_divide</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">/</span> <span class="n">arg2</span>
<span class="nd">@only_natural_with_operator</span>
<span class="nd">@calc_cor</span><span class="p">(</span><span class="n">my_operator</span><span class="o">=</span><span class="s1">'*'</span><span class="p">,</span> <span class="nb">next</span><span class="o">=</span><span class="n">_divide</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_multiply</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">*</span> <span class="n">arg2</span>
<span class="nd">@only_natural_with_operator</span>
<span class="nd">@calc_cor</span><span class="p">(</span><span class="n">my_operator</span><span class="o">=</span><span class="s1">'-'</span><span class="p">,</span> <span class="nb">next</span><span class="o">=</span><span class="n">_multiply</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_subtract</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">-</span> <span class="n">arg2</span>
<span class="nd">@only_natural_with_operator</span>
<span class="nd">@calc_cor</span><span class="p">(</span><span class="n">my_operator</span><span class="o">=</span><span class="s1">'+'</span><span class="p">,</span> <span class="nb">next</span><span class="o">=</span><span class="n">_subtract</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_add</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">+</span> <span class="n">arg2</span>
</pre></div>
<p>That looks way better than what we had before. The <code>do</code> method just calls the first link in the chain. Each chain link just does its processing. Everything else is just declared as decorator arguments and we are done. This combination of COR and Decorator pattern using decorators seems to have produced the best results. Wouldn't you agree?</p>
<h3 id="centralized-cor-30">Centralized CoR -3.0!?!</h3>
<p>I showed this result to Sathia, a <a href="https://twitter.com/sathia27">friend</a> and colleague of mine (that was different production code but the concept is the same). He pointed out something. With this design somebody trying to add a new operator has to figure out where in the chain she needs to add it. Rather she has to figure out where the current chain ends and add the new one there. In the above example that is simple. In real world code this may or may not be easy. He wanted to see if there is a way for each operator function to just register itself and then the chain would execute them. Of course in all these cases we are talking with the underlying premise that the operator processing order does not matter.</p>
<p>So some more thinking is needed. Can we make the CoR satisfy this? For this, we probably have to give away the decentralized nature of the current CoR. We need some kind of centralization. We need some way to register operation functions to a common place and then <em>pull the chain</em> to execute them all. Here goes another shot.</p>
<div class="highlight"><pre><span></span><span class="n">CHAIN</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">def</span> <span class="nf">link_to_chain</span><span class="p">(</span><span class="n">predicate</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="n">operator</span><span class="p">):</span>
<span class="n">CHAIN</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="n">predicate</span><span class="p">,</span><span class="n">operator</span><span class="p">))</span>
<span class="k">return</span> <span class="n">operator</span>
<span class="k">return</span> <span class="n">wrapper</span>
<span class="k">def</span> <span class="nf">pull_chain</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">):</span>
<span class="k">for</span> <span class="n">predicate</span><span class="p">,</span><span class="n">operator</span> <span class="ow">in</span> <span class="n">CHAIN</span><span class="p">:</span>
<span class="k">if</span> <span class="n">predicate</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">):</span>
<span class="k">return</span> <span class="n">operator</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s1">'Calc Error - Operator not supported'</span><span class="p">)</span>
</pre></div>
<p>The first function <code>link_to_chain</code> is a decorator function which registers (or links) the <em>operator</em> and its <em>predicate</em> into a registry or chain <code>CHAIN</code>. The predicate is nothing but a function or lambda which provides applicability check. In this case the decorator is not really doing any decoration (no pre or post processing). It is more a way to <a href="https://realpython.com/primer-on-python-decorators/#registering-plugins">plug things</a> in.</p>
<p>The <code>pull_chain</code> function executes the CoR. It runs through the chain of handlers, uses the registered predicate to find the right one, executes them and breaks out of the chain. If there is none found then it <em>raises</em>.</p>
<p>With this idea now the calculator looks like this</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">do</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">return</span> <span class="n">pull_chain</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>
<span class="nd">@link_to_chain</span><span class="p">(</span><span class="k">lambda</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">==</span><span class="s1">'^'</span><span class="p">)</span>
<span class="nd">@only_natural_with_operator</span>
<span class="k">def</span> <span class="nf">_power</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">**</span> <span class="n">arg2</span>
<span class="nd">@link_to_chain</span><span class="p">(</span><span class="k">lambda</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">==</span><span class="s1">'*'</span><span class="p">)</span>
<span class="nd">@only_natural_with_operator</span>
<span class="k">def</span> <span class="nf">_multiply</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">*</span> <span class="n">arg2</span>
<span class="nd">@link_to_chain</span><span class="p">(</span><span class="k">lambda</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">==</span><span class="s1">'/'</span><span class="p">)</span>
<span class="nd">@only_natural_with_operator</span>
<span class="k">def</span> <span class="nf">_divide</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">/</span> <span class="n">arg2</span>
<span class="nd">@link_to_chain</span><span class="p">(</span><span class="k">lambda</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">==</span><span class="s1">'+'</span><span class="p">)</span>
<span class="nd">@only_natural_with_operator</span>
<span class="k">def</span> <span class="nf">_add</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">+</span> <span class="n">arg2</span>
<span class="nd">@link_to_chain</span><span class="p">(</span><span class="k">lambda</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">==</span><span class="s1">'-'</span><span class="p">)</span>
<span class="nd">@only_natural_with_operator</span>
<span class="k">def</span> <span class="nf">_subtract</span><span class="p">(</span><span class="n">operator</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">-</span> <span class="n">arg2</span>
</pre></div>
<p>Not bad at all! Actually looks pretty good to me. Individual functions link to the chain and the <code>do</code> method pulls the chain. The single responsibility of the functions shine through. Given that the chain is centralized, the individual functions don't even care about who is next. All they do is register into the chain along with their predicate. This is probably the cleanest solution we can get as of now. Time to stop and take a break!</p>
<h2 id="closing-thoughts">Closing thoughts</h2>
<p>Python is great language with support for useful functional features. Decorators are a sweet way of doing functional programming in python. The great thing I realized in this attempt is, when we try to do functional programming, your earlier learnings of GOF design patterns don't go waste. The ideas still make sense. All we need to do was to tweak them a bit to make them applicable in a functional context. And lo behold we have much better code than where we started ...the <code>if/elif/else</code> blob in the beginning... Let us keep learning and try to extract out the essence of the things we learn. Then we might actually be able use it in more than one places and in more than one ways.</p>
<p>Happy development to all in the festive season! Please chime in with your thoughts and comments. Your criticisms and improvements are most welcome since I learn a lot from them. See you soon.</p>
<p>p.s: You can get all this code <a href="http://bit.ly/2mMojaa">here</a></p>
</div>
<!-- /.entry-content -->
<hr/>
<section class="comments" id="comments">
<h2>Comments</h2>
<div id="disqus_thread"></div>
<script type="text/javascript">
/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
var disqus_shortname = 'nacnez'; // required: replace example with your forum shortname
var disqus_config = function () {
this.language = "en";
this.page.identifier = '2019-09-29-gof-inspired-decorators';
this.page.url = 'https://www.nacnez.com/gof-inspired-decorators.html';
};
/* * * DON'T EDIT BELOW THIS LINE * * */
(function () {
var dsq = document.createElement('script');
dsq.type = 'text/javascript';
dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by
Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
</section>
</article>
</section>
</div>
<div class="col-sm-3" id="sidebar">
<aside>
<div id="aboutme">
<p>
<img width="100%" class="img-thumbnail" src="https://www.nacnez.com/images/profile.jpeg"/>
</p>
<p>
<strong>About Srinivas Narayanan (Srini)</strong><br/>
Living in Bengaluru. Family man & Software engineer. Currently I am a <span class="bold-green"><a href="https://getsimpl.com/"><img src="https://www.google.com/s2/favicons?domain=https://getsimpl.com">Simpl</a></span>fier. <br/>This, a place for me to write about anything under the 🌞. <span class="bold-angry">My views are mine alone</span> & not related to any person or organisation including my employer.
</p>
</div><!-- Sidebar -->
<section class="well well-sm">
<ul class="list-group list-group-flush">
<!-- Sidebar/Social -->
<li class="list-group-item">
<h4><i class="fa fa-home fa-lg"></i><span class="icon-label">Social</span></h4>
<ul class="list-group" id="social">
<li class="list-group-item"><a href="http://twitter.com/srininara"><i class="fa fa-twitter-square fa-lg"></i> twitter</a></li>
<li class="list-group-item"><a href="http://github.com/srininara"><i class="fa fa-github-square fa-lg"></i> github</a></li>
<li class="list-group-item"><a href="https://www.linkedin.com/in/srinivas-narayanan-1912b14/"><i class="fa fa-linkedin-square fa-lg"></i> linkedin</a></li>
<li class="list-group-item"><a href="https://www.nacnez.com/feeds/all.atom.xml"><i class="fa fa-rss-square fa-lg"></i> rss</a></li>
</ul>
</li>
<!-- End Sidebar/Social -->
<!-- Sidebar/Tag Cloud -->
<li class="list-group-item">
<a href="https://www.nacnez.com/"><h4><i class="fa fa-tags fa-lg"></i><span class="icon-label">Tags</span></h4></a>
<ul class="list-group " id="tags">
<li class="list-group-item tag-1">
<a href="https://www.nacnez.com/tag/life.html">life</a>
</li>
<li class="list-group-item tag-1">
<a href="https://www.nacnez.com/tag/development.html">development</a>
</li>
<li class="list-group-item tag-1">
<a href="https://www.nacnez.com/tag/software-engineering.html">software-engineering</a>
</li>
<li class="list-group-item tag-2">
<a href="https://www.nacnez.com/tag/architecture.html">architecture</a>
</li>
<li class="list-group-item tag-2">
<a href="https://www.nacnez.com/tag/people.html">people</a>
</li>
<li class="list-group-item tag-2">
<a href="https://www.nacnez.com/tag/python.html">python</a>
</li>
<li class="list-group-item tag-2">
<a href="https://www.nacnez.com/tag/microservices.html">microservices</a>
</li>
<li class="list-group-item tag-2">
<a href="https://www.nacnez.com/tag/technology.html">technology</a>
</li>
<li class="list-group-item tag-4">
<a href="https://www.nacnez.com/tag/teaching.html">teaching</a>
</li>
<li class="list-group-item tag-4">
<a href="https://www.nacnez.com/tag/education.html">education</a>
</li>
</ul>
</li>
<!-- End Sidebar/Tag Cloud -->
</ul>
</section>
<!-- End Sidebar --> </aside>
</div>
</div>
</div>
<!-- End Content Container -->
<footer>
<div class="container">
<hr>
<div class="row">
<div class="col-xs-10">© 2023 Srinivas Narayanan (Srini)
· Powered by <a href="https://github.com/getpelican/pelican-themes/tree/master/pelican-bootstrap3" target="_blank">pelican-bootstrap3</a>,
<a href="http://docs.getpelican.com/" target="_blank">Pelican</a>,
<a href="http://getbootstrap.com" target="_blank">Bootstrap</a> </div>
<div class="col-xs-2"><p class="pull-right"><i class="fa fa-arrow-up"></i> <a href="#">Back to top</a></p></div>
</div>
</div>
</footer>
<script src="https://www.nacnez.com/theme/js/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://www.nacnez.com/theme/js/bootstrap.min.js"></script>
<!-- Enable responsive features in IE8 with Respond.js (https://github.com/scottjehl/Respond) -->
<script src="https://www.nacnez.com/theme/js/respond.min.js"></script>
<!-- Disqus -->
<script type="text/javascript">
/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
var disqus_shortname = 'nacnez'; // required: replace example with your forum shortname
/* * * DON'T EDIT BELOW THIS LINE * * */
(function () {
var s = document.createElement('script');
s.async = true;
s.type = 'text/javascript';
s.src = '//' + disqus_shortname + '.disqus.com/count.js';
(document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s);
}());
</script>
<!-- End Disqus Code -->
</body>
</html>