tag:blogger.com,1999:blog-96484222024-03-08T00:03:04.636+02:00Becoming AgileMy insight on the business of software development, expecially in agile methodolgies and lately into functional programmingDan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.comBlogger64125tag:blogger.com,1999:blog-9648422.post-13261920208370934392023-07-28T11:52:00.003+03:002023-07-28T11:52:36.937+03:00Datomic as-of and since, in practice<p> Considering we have a simple Datomic database, where we do 3 transactions:</p><p>First we add an app. Then we add a chat "c1" in that app. Then we add another chat "c2" in the same app.</p><p>Looking at the transaction history:</p><div><br /></div><p><br /></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEj-4qE6fg6XIQdTreOT8hvCMIDtUIGleOJBNhr6kaLhqrmAcpgTo152ilMhpVjN1yeec3c2Xt5564t5ivM0hyCLmIElJFxqTigt3LGaSW9RPIlDM-zWAFJbaiHSTZzAuRAg66QtoPJWz4yf4c-6Y-vooCTfM1PK1hSMBHAxdGu7BFXb0wGBBSP0" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="1254" data-original-width="2662" height="302" src="https://blogger.googleusercontent.com/img/a/AVvXsEj-4qE6fg6XIQdTreOT8hvCMIDtUIGleOJBNhr6kaLhqrmAcpgTo152ilMhpVjN1yeec3c2Xt5564t5ivM0hyCLmIElJFxqTigt3LGaSW9RPIlDM-zWAFJbaiHSTZzAuRAg66QtoPJWz4yf4c-6Y-vooCTfM1PK1hSMBHAxdGu7BFXb0wGBBSP0=w640-h302" width="640" /></a></div><br /><br /><p></p><p>Now we can pull, at the different times of the transactions, using as-of meaning we'll only see the results as they were at that time:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjYydpN7O102T9qEsqPNW8SdfTCKOInITEkOubVtdIF5lKvTAuvNtyr_iwgv8cUbJ5x0FTv2w2IhfZgG2rai9uk02UxWrVZ7yIS-bgO-qbLWzAHgchZTVj_wXvSfh-vKgRkccmBs4-A26R9gRx53CgrN8TfgbAM4sgKShV8o57ulkvrnLP9q4O3" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="1310" data-original-width="2668" height="314" src="https://blogger.googleusercontent.com/img/a/AVvXsEjYydpN7O102T9qEsqPNW8SdfTCKOInITEkOubVtdIF5lKvTAuvNtyr_iwgv8cUbJ5x0FTv2w2IhfZgG2rai9uk02UxWrVZ7yIS-bgO-qbLWzAHgchZTVj_wXvSfh-vKgRkccmBs4-A26R9gRx53CgrN8TfgbAM4sgKShV8o57ulkvrnLP9q4O3=w640-h314" width="640" /></a></div><br />But another interesting thing, is seeing the changes since a particular time using since (think you're asking only for changes since a particular time):<p></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjU3vrWlG8ZVciqgTxAIilgAyZQNGmqvtJtySKkHidXg54WLtOsW7fFEMjwDC-KJRNwIBNTWp4ryrNch5FU_TppawI41qCG170tAx9ppgre9GrgGCisuSKOTxwtYep0w52goTlwgpIJuASP_cDUncll6vi20aipDa5w-XnUfla3owP8LKy-EXIQ" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="1200" data-original-width="2636" height="293" src="https://blogger.googleusercontent.com/img/a/AVvXsEjU3vrWlG8ZVciqgTxAIilgAyZQNGmqvtJtySKkHidXg54WLtOsW7fFEMjwDC-KJRNwIBNTWp4ryrNch5FU_TppawI41qCG170tAx9ppgre9GrgGCisuSKOTxwtYep0w52goTlwgpIJuASP_cDUncll6vi20aipDa5w-XnUfla3owP8LKy-EXIQ=w640-h293" width="640" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-11142036796719931092019-09-21T14:22:00.000+03:002019-09-21T18:02:17.885+03:00How to solve it? Complexity in code - Improving flow control through functional pipelines<div style="font-size: 12px; font-stretch: normal; line-height: normal;">
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Code: <a href="https://gitlab.com/danbunea/improving-control-flow-in-code-using-functional-pipelines">https://gitlab.com/danbunea/improving-control-flow-in-code-using-functional-pipelines</a></span><br />
<br />
<h2>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Why?</span></h2>
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></div>
<div style="font-stretch: normal; line-height: normal;">
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">You:</span></div>
<div style="font-stretch: normal; line-height: normal;">
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>Why?</i></span></div>
<div style="font-stretch: normal; line-height: normal;">
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Me:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>Because code is better...</i></span></div>
<div style="font-stretch: normal; line-height: normal;">
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">You:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>Code is better? How?</i></span></div>
<div style="font-stretch: normal; line-height: normal;">
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Me:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>Because is it easier to: </i></span></div>
<ul>
<li style="font-stretch: normal; line-height: normal; margin: 0px;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>read</i></span></li>
<li style="font-stretch: normal; line-height: normal; margin: 0px;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>extend</i></span></li>
<li style="font-stretch: normal; line-height: normal; margin: 0px;"><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>debug</i></span></li>
</ul>
<div style="font-stretch: normal; line-height: normal;">
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">You:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>Prove it!</i></span></div>
<div style="font-stretch: normal; line-height: normal;">
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Me:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>Ok let's look at the following problem:</i></span></div>
<div style="font-stretch: normal; line-height: normal;">
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></div>
<h2>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">In practice</span></h2>
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></div>
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">We have to write an endpoint which returns an offer by its id:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /><b>GET /offer-by-id/:offer-id</b></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Possible results:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /><i>- 200 {"offer-id":2, "offer-data":""}<br />- 400 {"errors":["The id you provided is invalid"}]}<br />- 404 {"errors":["The id you provided cannot be found"}]}</i></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">IMPORTANT: if an id is valid but not found the deposit must be notified!</span></div>
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></div>
<h3>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Solution 1: IFs</span></h3>
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">The problem could be solved by:</span></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdd6-u8AOF3a4Ufgvkgz4sWtb_w_5jGoq14GMYnnYdOkIQNo3Suy2lqhMDe9s2HLraW9-URZAtRUZaHCtEW8wZ3Dn2to12F0dAsf1OY_u0HQ_nfCuBVWjB0ZS1txq48zr5yl60/s1600/ifs.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1254" data-original-width="1056" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdd6-u8AOF3a4Ufgvkgz4sWtb_w_5jGoq14GMYnnYdOkIQNo3Suy2lqhMDe9s2HLraW9-URZAtRUZaHCtEW8wZ3Dn2to12F0dAsf1OY_u0HQ_nfCuBVWjB0ZS1txq48zr5yl60/s640/ifs.png" width="537" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Or in code:</span></div>
<br />
<iframe allowfullscreen="allowfullscreen" allowpaymentrequest="" frameborder="0" height="900" src="//jsfiddle.net/danbunea1/3ak9z87h/embedded/js,result/dark/" width="100%"></iframe>
<br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Whenever we have ifs in code, it becomes dificult to read so maybe we could simplify it, making it more like:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<b><u><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">step 1</span></u></b><br />
<u><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">then <b>step 2</b></span></u><br />
<u><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">then <b>step 3</b></span></u><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Or more like</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<b><u><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">validate</span></u></b><br />
<u><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">then <b>find offer</b></span></u><br />
<u><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">then <b>jsonify</b></span></u><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Hmm, can we?</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<h3>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Solution 2: Exceptions</span></h3>
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">We'll use exceptions to break out flow:</span><br />
<br />
<iframe allowfullscreen="allowfullscreen" allowpaymentrequest="" frameborder="0" height="700" src="//jsfiddle.net/danbunea1/qhnromg3/embedded/js,result/dark/" width="100%"></iframe>
<br />
<br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">The solution above is very present in Java, even if the way the errors are caught might not be this explicit.</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Because it's a fairly simple example we could also use the strategy pattern. Basically we use ifs to choose a strategy, then we execute it to give us the result. </span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<h3>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Solution 3: strategy pattern</span></h3>
<br />
<br />
<iframe allowfullscreen="allowfullscreen" allowpaymentrequest="" frameborder="0" height="1000" src="//jsfiddle.net/danbunea1/Lmsv4tw5/embedded/js,result/dark/" width="100%"></iframe>
<br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Functional programming gives us sever possibilities using pipes. </span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">What is a pipe?</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">A pipe makes sure that steps get executed in a certain order and that the result of a step is passed to the next step. Like:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgq8PRlzRTgRr0O5xK8HvzrldfGmqEdSwHv5-iL1KMryR0ASJUvXKPlIXnjMN3Zce-cW_rrGZaNZ4zdmPx4iTcpPCAf0DKNnCjwhrucamoje5zmxb0PxRGG-IwjeUBxL_xLTutR/s1600/flow.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1200" data-original-width="914" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgq8PRlzRTgRr0O5xK8HvzrldfGmqEdSwHv5-iL1KMryR0ASJUvXKPlIXnjMN3Zce-cW_rrGZaNZ4zdmPx4iTcpPCAf0DKNnCjwhrucamoje5zmxb0PxRGG-IwjeUBxL_xLTutR/s320/flow.gif" width="243" /></a></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">In our case we'd like something like:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>validate(requestId)</i></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>.then(findOfferById)</i></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>.then(jsonify)</i></span><br />
<br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Solution 4 </span><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Functional either or railway oriented programming</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">This is a very common solution in typed functional languages such as F# or Haskel, but it's becoming very common in Java as well.</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Having a single pipeline is very beautiful but how do we handle errors? By using two parallel pipes. A pipe for the happy path and a path for the errors. Basically all our functions can return <b>either</b> a SuccesfullResponse or a ErrorResponse. This will get passed on to the next function that will process it and return again <b>either</b></span><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"> a SuccesfullResponse or a ErrorResponse.</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">In F# we'd have something like:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<i><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">findOrderById: IResponse -> </span><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">SuccesfullResponse | </span><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">ErrorResponse</span></i><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">we might describe it like:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<i><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">SuccesfullResponse | </span><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">ErrorResponse </span><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">aFunction(</span><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">SuccesfullResponse | </span><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">ErrorResponse response)</span></i><br />
<i><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></i>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">For the happy path we'd have:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkVgfsISN_gid-7gWYQXk7VhpBOOtCzMbM8fo0DFjw5i50cLIMdLUVuok1zeQVmWNtelqQpR0WA3xwcth_qxMVRxy6K1W4tpE5dJRUTJPcXludxHXnaqm6dUqmAYzgXV9hHegC/s1600/either+succes.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1200" data-original-width="914" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkVgfsISN_gid-7gWYQXk7VhpBOOtCzMbM8fo0DFjw5i50cLIMdLUVuok1zeQVmWNtelqQpR0WA3xwcth_qxMVRxy6K1W4tpE5dJRUTJPcXludxHXnaqm6dUqmAYzgXV9hHegC/s320/either+succes.gif" width="243" /></a></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">And if we get a validation error in the first step we'll have:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSmNvaGwxD0_k7iZojmT2kNbbqPfaULTSLzBrnY-k__C0CjI6gK9Gb8tEgll-DlhOLVWpe28REhQgUceo9QYNdeHWE7foPG_dwkl70rJdOt4EtenPWjpZb9d9bqNliaMfhPwUE/s1600/either+error.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1200" data-original-width="914" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSmNvaGwxD0_k7iZojmT2kNbbqPfaULTSLzBrnY-k__C0CjI6gK9Gb8tEgll-DlhOLVWpe28REhQgUceo9QYNdeHWE7foPG_dwkl70rJdOt4EtenPWjpZb9d9bqNliaMfhPwUE/s320/either+error.gif" width="243" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">And the code, will have a class for Success and a class for Error. Each will inherit an IResponse and will implement two functions receiving a function (lambda) then and fail. In Succes we'll return the result of applying the function on what we have on then and the data we have on fail, and on Error we'll do exactly the opposite. </span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><u>Imaging you'd replace then with map and fail with orElseGet, doesn't that sound like an optional?</u></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<iframe allowfullscreen="allowfullscreen" allowpaymentrequest="" frameborder="0" height="1550" src="//jsfiddle.net/danbunea1/zjfyneaq/embedded/js,result/dark/" width="100%"></iframe>
<br />
<br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">In dynamic languages, there are other options. But first let's describe functional composition. </span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><b>Functional composition</b> is when you combine 2 (or more) functions into one, then apply it. It's pretty much like pipe but you may do the composition at runtime. </span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Now we can look at the two options. First is:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<h3>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Solution 5: pipeline with flag</span></h3>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">We'll pass through the pipe an object that contains a flag which tells you whether there were errors before. Basically using a value in your data instead of using the type of the data (has response property instead of type: Successful or Error).</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifhny_jiWrjjtUapQtnhLqykOsihACXcnpzTnlF2Cqe865wq2raMLe8wCuVsPANPomDU_9V7VcGklpgK2gqS6U_DqaAkbrmK10A7B4UevIHnJOaw6i0ScwYVKV0zSuewnULEwW/s1600/functional+flag.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1200" data-original-width="914" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifhny_jiWrjjtUapQtnhLqykOsihACXcnpzTnlF2Cqe865wq2raMLe8wCuVsPANPomDU_9V7VcGklpgK2gqS6U_DqaAkbrmK10A7B4UevIHnJOaw6i0ScwYVKV0zSuewnULEwW/s320/functional+flag.gif" width="243" /></a></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">In our case, the flag is whether a response has been set already:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>if(state.response) return state;</i></span><br />
<br />
<iframe allowfullscreen="allowfullscreen" allowpaymentrequest="" frameborder="0" height="1300" src="//jsfiddle.net/danbunea1/bfueqwz0/embedded/js,result/dark/" width="100%"></iframe>
<br />
<br />
<h3>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Solution 6: pipeline + overflow pipeline </span></h3>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">The second option is to have a pipeline and an overflow pipeline. </span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvK6sK1AonnP6WmQ-nHBGxrsKUhbfoVCS5jk2aul2VxJtPzCMUmzcI0cWwPC-98hd3Lat4OyJdzrKoLdADvP1KJ5GE0CjSmKYywYD013boJrroOBYwlAKo3FR99XYRAuT9JY5j/s1600/pipe+normal+flow.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1200" data-original-width="914" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvK6sK1AonnP6WmQ-nHBGxrsKUhbfoVCS5jk2aul2VxJtPzCMUmzcI0cWwPC-98hd3Lat4OyJdzrKoLdADvP1KJ5GE0CjSmKYywYD013boJrroOBYwlAKo3FR99XYRAuT9JY5j/s320/pipe+normal+flow.gif" width="243" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">We'll use exceptions once again to bypass the normal pipeline and go to the overflow pipeline.</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOFpckfV5N_1COkiy2K6fEpyWW3_t_6dhamLRjMSX7brX2hGZYXk1fjmbdCUikMrjMGKAZ7xGkpQONsV8V1ovTm2xY2B72Xu3gOGodU3QHj1rdjnOW0L6bTuDReg3xS0TUPsls/s1600/pipe+overflow.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1200" data-original-width="914" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOFpckfV5N_1COkiy2K6fEpyWW3_t_6dhamLRjMSX7brX2hGZYXk1fjmbdCUikMrjMGKAZ7xGkpQONsV8V1ovTm2xY2B72Xu3gOGodU3QHj1rdjnOW0L6bTuDReg3xS0TUPsls/s320/pipe+overflow.gif" width="243" /></a></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Unlike the previous two options when an exception happend it will jump straight to the end, bypassing the next steps directly.</span><br />
<br />
<iframe allowfullscreen="allowfullscreen" allowpaymentrequest="" frameborder="0" height="1200" src="//jsfiddle.net/danbunea1/2n6g8xz4/embedded/js,result/dark/" width="100%"></iframe><br />
<br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">The code: </span><a href="https://gitlab.com/danbunea/improving-control-flow-in-code-using-functional-pipelines/tree/stage-1">https://gitlab.com/danbunea/improving-control-flow-in-code-using-functional-pipelines/tree/stage-1</a><br />
<br />
<h2>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Readability</span></h2>
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">In terms of readability it could be a lot easier, to see the code as a pipeline:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>step 1</i></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>then step 2</i></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>then step 3</i></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">While also handling the errors:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>step 1</i></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>then step 2</i></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>then step 3</i></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>fail on-error</i></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">or </span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>safe(</i></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i> step 1</i></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i> then step 2</i></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i> then step 3</i></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>)</i></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"></span>
<br />
<h2>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Extensibility</span></h2>
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">But what about extensibility?</span><br />
<br />
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">We now have to modify our endpoint</span><br />
<br />
<ol>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">to check if the offers are still active. If the aren't we need to return an error</span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">to update the number of times the offer has been accessed</span></li>
</ol>
<br />
<br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><b>GET /offer-by-id/:offer-id</b></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><b><br /></b></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Possible results:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /><i>- 200 {"offer-id":2, "offer-data":"", <span style="color: #990000;">"active":true, </span><span style="color: #0b5394;">"requests":1</span>}<br /><span style="color: red;">- 400 {"errors":["The id you provided is invalid"}]}</span></i></span><br />
<span style="color: #990000; font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>- 400 {"errors":["The offer expired"}]}</i></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i>- 404 {"errors":["The id you provided cannot be found"}]}</i></span><br />
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></div>
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><i><br /></i></span></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">IMPORTANT: if an id is valid but the offer expired the deposit must be notified!</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">We changed the tests:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Then we change the code and we can look at how</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">For solution 1:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7tRMtqJU5tRe-X9v6AjwJRuZpk8-O3B8B5CS1V0ct0wSOkLyJ4-oF33wcLm7JGXQWOaISTF0w0Al27brOOaHMRAzRCcpQobioza4YNyttiHiIEiB6stpWJZVb8t3UWlcqT1U9/s1600/1+ifs.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="723" data-original-width="1600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7tRMtqJU5tRe-X9v6AjwJRuZpk8-O3B8B5CS1V0ct0wSOkLyJ4-oF33wcLm7JGXQWOaISTF0w0Al27brOOaHMRAzRCcpQobioza4YNyttiHiIEiB6stpWJZVb8t3UWlcqT1U9/s1600/1+ifs.png" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">The way we solved it was to add more ifs inside an existing if thus increasing the cyclomatic complexity of the solution making it even harder to read. And real life code tends to be more complex than this.</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">For solution 2:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdBGxzflpZ4dgRXl8EdENY9SujU7hwN1th0T77puCjwPIXThPjHtriTWLlGj3zkzqO5iOsH60q5fB9yponSsqbTBM0FOY4xXWGrYaODSQvGMBknDBCA_iOKhMMHxr9S2TM4a4l/s1600/2+exceptions.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1014" data-original-width="1600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdBGxzflpZ4dgRXl8EdENY9SujU7hwN1th0T77puCjwPIXThPjHtriTWLlGj3zkzqO5iOsH60q5fB9yponSsqbTBM0FOY4xXWGrYaODSQvGMBknDBCA_iOKhMMHxr9S2TM4a4l/s1600/2+exceptions.png" /></a></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">We did:</span><br />
<br />
<ul>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">added a new exception</span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">changed the code inside the try catch block</span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">changed the code in the catch</span></li>
</ul>
<br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">For solution 4:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiT1UI75s9WjbwMtjvTPAXlEThnZZHvzLjtXc8spMYOgPfLphg9pGIqgG-rx-Av6pCsaMU8KFBM-CZ5hWiYNpYLJi_lf96v4EwVy540PFS1Mabxn278LNerDBDtnmrGYGpJpvIS/s1600/4+either+better.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="963" data-original-width="1600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiT1UI75s9WjbwMtjvTPAXlEThnZZHvzLjtXc8spMYOgPfLphg9pGIqgG-rx-Av6pCsaMU8KFBM-CZ5hWiYNpYLJi_lf96v4EwVy540PFS1Mabxn278LNerDBDtnmrGYGpJpvIS/s1600/4+either+better.png" /></a></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">What did we do:</span><br />
<br />
<ul>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">added two new functions, completely independent </span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><b>modified an existing one</b></span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">added steps to the pipeline</span></li>
</ul>
<br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">For solution 5:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvvaDKW3JvXsrzp9lHNujMg_GkVHeBA8vIrgMdT3Qlw_mruqn2V_ftDATe6-TOHU-JjCPoDn5ywfzvNSHL2yyHgIPc9Msp-J8lRECTL6Jn9sG6x_R3M7cFIgaYA3Sd4jQcTymh/s1600/5+flag.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1018" data-original-width="1600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvvaDKW3JvXsrzp9lHNujMg_GkVHeBA8vIrgMdT3Qlw_mruqn2V_ftDATe6-TOHU-JjCPoDn5ywfzvNSHL2yyHgIPc9Msp-J8lRECTL6Jn9sG6x_R3M7cFIgaYA3Sd4jQcTymh/s1600/5+flag.png" /></a></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">What did we do:</span><br />
<br />
<ul>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">added a new function</span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><b><span style="color: red;">heavily modified an existing one</span></b></span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">added a step to the pipeline</span></li>
</ul>
<br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">For solution 6:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPKy6lHPT_n0F9mjaWFaYvU1ULPaL5aWEGdwS5ahfwFcuECsiDR5IqQ3hCUhdxnq8xKen2mityNIHhlCzETmnauHdCzlWegdlSYiftiiorX39aAqVYSYrFAneb6ZQyH6YeRUSh/s1600/6+overflow.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="748" data-original-width="1600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPKy6lHPT_n0F9mjaWFaYvU1ULPaL5aWEGdwS5ahfwFcuECsiDR5IqQ3hCUhdxnq8xKen2mityNIHhlCzETmnauHdCzlWegdlSYiftiiorX39aAqVYSYrFAneb6ZQyH6YeRUSh/s1600/6+overflow.png" /></a></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">What did we do:</span><br />
<br />
<ul>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">added a two new functions</span></li>
<li><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">added them as steps to the pipeline</span></li>
</ul>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><b>No existing code modified! </b>(except for the pipeline, which is expected)</span><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">The code: <a href="https://gitlab.com/danbunea/improving-control-flow-in-code-using-functional-pipelines/tree/stage-2">https://gitlab.com/danbunea/improving-control-flow-in-code-using-functional-pipelines/tree/stage-2</a></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span><span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">The merge request in gitlab: <a href="https://gitlab.com/danbunea/improving-control-flow-in-code-using-functional-pipelines/merge_requests/3/diffs#faff669626dfa73714964353a02a5101dce1b3a7">https://gitlab.com/danbunea/improving-control-flow-in-code-using-functional-pipelines/merge_requests/3/diffs#faff669626dfa73714964353a02a5101dce1b3a7</a></span><br />
<br />
<h2>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Debugging</span></h2>
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></div>
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Let's say we need to start logging what is going on. For ifs we may end up with:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIG60_B00KLBHUWRu0JBCxJeoI9bKssv5rRerEBo07D7-BUZMXXj595BgEX3QIH8iAP2fUiIwzlJ4LjS5eyuIdhBsFnzPzgP72FNo9Yqpp__pzTSGAu1fhBm-u1bsHAzVE60pM/s1600/logging+ifs.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1412" data-original-width="995" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIG60_B00KLBHUWRu0JBCxJeoI9bKssv5rRerEBo07D7-BUZMXXj595BgEX3QIH8iAP2fUiIwzlJ4LjS5eyuIdhBsFnzPzgP72FNo9Yqpp__pzTSGAu1fhBm-u1bsHAzVE60pM/s640/logging+ifs.png" width="450" /></a></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Not exactly easy to know where to insert the logging. </span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">But for a pipeline, we just need to insert some tracing function between the steps:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHwZikgtklkhqJiZzPNPX4rrBfXdm02y_rNAQiq4z7MTRfuIiTFL2mj_bzhWpVjPuHlA0Pvmx605av5mothf4EN87w_q0s9W8ypQ1TQwHhwt8136bKXmaI6YSJ2sSIcs1nqvSI/s1600/logging+either.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="636" data-original-width="978" height="416" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHwZikgtklkhqJiZzPNPX4rrBfXdm02y_rNAQiq4z7MTRfuIiTFL2mj_bzhWpVjPuHlA0Pvmx605av5mothf4EN87w_q0s9W8ypQ1TQwHhwt8136bKXmaI6YSJ2sSIcs1nqvSI/s640/logging+either.png" width="640" /></a></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">giving us:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhltLf_-6-h1ninpK44qOm-cHWmJgcFh5fISdDRGWyqyTCrydDyDCYXQi4Fumyy0Bdm8Ja_7gDimg40oFG5vl2guzR9YgmT5YAZZP9pcrVzBn5m7Mp4IsxBCjQF-xA9Q96zOpyT/s1600/either+log.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="734" data-original-width="1600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhltLf_-6-h1ninpK44qOm-cHWmJgcFh5fISdDRGWyqyTCrydDyDCYXQi4Fumyy0Bdm8Ja_7gDimg40oFG5vl2guzR9YgmT5YAZZP9pcrVzBn5m7Mp4IsxBCjQF-xA9Q96zOpyT/s1600/either+log.png" /></a></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">or</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYurYj04ajp1LYvCc525OzSo1h1v_QYqRn6PYtdEetNGvghLNB-cgXXZ2Ck2iD-vMx9O45_b1h9hPJxR46gcHpHxfclfpUecXtl1v15hRj8hmFdnyX6RsGmlStjfTn2NvHmV37/s1600/logging+pipelines.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="584" data-original-width="956" height="390" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYurYj04ajp1LYvCc525OzSo1h1v_QYqRn6PYtdEetNGvghLNB-cgXXZ2Ck2iD-vMx9O45_b1h9hPJxR46gcHpHxfclfpUecXtl1v15hRj8hmFdnyX6RsGmlStjfTn2NvHmV37/s640/logging+pipelines.png" width="640" /></a></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">And the log is like:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgm4TMdOPJ_m_Vht5xu78l3RIAc3jOznlbfsZddzHMnngrutLSuLqMF5sIbf1WX6mRrr4CJFhyphenhyphenA5dkpJGOSPPTJUmMl58f4WPHp9nB-ZjQSf7yr-p3x7gFdD28otomwydCpgOg7/s1600/pipelines+log.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="555" data-original-width="1600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgm4TMdOPJ_m_Vht5xu78l3RIAc3jOznlbfsZddzHMnngrutLSuLqMF5sIbf1WX6mRrr4CJFhyphenhyphenA5dkpJGOSPPTJUmMl58f4WPHp9nB-ZjQSf7yr-p3x7gFdD28otomwydCpgOg7/s1600/pipelines+log.png" /></a></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></div>
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Last, let's see where pipelines could be used.</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Frontend/Javascript with Promises:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjX0g-DLeN2SeMGtHNi71q6eXd0PjF1WS2BSP0XiOD6hP8eH4zmofSr9Y2QPPm5cFdv0Mt5jo-qxOS5EqyVNl88ufmvCXK2jRLb8vdI7-AX_B_utGmDhAHCQXR5xu128hudvH6X/s1600/ex+javascript.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1128" data-original-width="1506" height="479" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjX0g-DLeN2SeMGtHNi71q6eXd0PjF1WS2BSP0XiOD6hP8eH4zmofSr9Y2QPPm5cFdv0Mt5jo-qxOS5EqyVNl88ufmvCXK2jRLb8vdI7-AX_B_utGmDhAHCQXR5xu128hudvH6X/s640/ex+javascript.png" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgum377Wysd1YNzI0TIevSj6tZKl63DVUSE6l-d4vqPD-TnTMqv2rSAVUY2XpgI_mn6T0ViWoDrvoVtGXpJoaddiFo4aJc33tktuofPAWU9z1CEyQwh7RPvlb8VrX8Q_GhBxNUR/s1600/ex+js+2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="728" data-original-width="1502" height="310" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgum377Wysd1YNzI0TIevSj6tZKl63DVUSE6l-d4vqPD-TnTMqv2rSAVUY2XpgI_mn6T0ViWoDrvoVtGXpJoaddiFo4aJc33tktuofPAWU9z1CEyQwh7RPvlb8VrX8Q_GhBxNUR/s640/ex+js+2.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">or front end Clojurescript:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjvl2ulacW8YQ0SMHn0_u43l8NqPEZHGqmi-fyvSf7d36SuVR_o5f_mdRIesPGdcCWWgVsrSNosQkO0bjK-gXd7X5jLqFyZKTmkpeycAwGOgCKLfsJOVDAGpEYYSFP-2HEraoF/s1600/ex+cljs.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1394" data-original-width="1596" height="558" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjvl2ulacW8YQ0SMHn0_u43l8NqPEZHGqmi-fyvSf7d36SuVR_o5f_mdRIesPGdcCWWgVsrSNosQkO0bjK-gXd7X5jLqFyZKTmkpeycAwGOgCKLfsJOVDAGpEYYSFP-2HEraoF/s640/ex+cljs.png" width="640" /></a></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">or backend Python:</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0XdNil0yfllvJTxNcVPLfHz2pdTP4ubIXW85_ezXbYqAw6sq4RL8TCldQm3L-FbM6GotMoV6qpIbBNBkF9FngU-Z0dqSNmRP7wlCO5Rt3-3ehZUROf9CATvsuQIhb3_RUu_ff/s1600/ex+python+backend.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1178" data-original-width="1170" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0XdNil0yfllvJTxNcVPLfHz2pdTP4ubIXW85_ezXbYqAw6sq4RL8TCldQm3L-FbM6GotMoV6qpIbBNBkF9FngU-Z0dqSNmRP7wlCO5Rt3-3ehZUROf9CATvsuQIhb3_RUu_ff/s640/ex+python+backend.png" width="634" /></a></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">or Java pipelines</span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUIrhixzHyeSD-8CD3O6InHwZONc0Q72ENfJ6_ivwywyDvnWgyOMiin3np_Zt_cY8XsTQadAhoXzjfESixUpIYHmMHPKVZuBozE1i_pvtccTQQ90_cn50ZhRHs-QtJUKnqqjEH/s1600/ex+java+pipeline.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="758" data-original-width="1464" height="331" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUIrhixzHyeSD-8CD3O6InHwZONc0Q72ENfJ6_ivwywyDvnWgyOMiin3np_Zt_cY8XsTQadAhoXzjfESixUpIYHmMHPKVZuBozE1i_pvtccTQQ90_cn50ZhRHs-QtJUKnqqjEH/s640/ex+java+pipeline.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">or</span></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNcUegcMyj7otSO2-SpGre9efF83EyPykCfLxlaDy_LOTgFNecQkdzsxQDlELhfm8-2vhPiM9AODdeOEpce2UQ3eqsBg-hETOcFITbcvsIjSBN18z6vAIe0PnNTNqSCIjUxKux/s1600/ex+java+endpoint.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="712" data-original-width="1332" height="340" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNcUegcMyj7otSO2-SpGre9efF83EyPykCfLxlaDy_LOTgFNecQkdzsxQDlELhfm8-2vhPiM9AODdeOEpce2UQ3eqsBg-hETOcFITbcvsIjSBN18z6vAIe0PnNTNqSCIjUxKux/s640/ex+java+endpoint.png" width="640" /></a></div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">The example comes from the book "Functional Style" by our colleague <a href="https://twitter.com/richardjwild" target="_blank">Richard Wild</a> <a href="https://functional.works-hub.com/learn/the-functional-style-part-5-higher-order-functions-i-function-composition-and-the-monad-pattern-bc74a?utm_source=blog&utm_medium=blog&utm_campaign=j.kaplan">https://functional.works-hub.com/learn/the-functional-style-part-5-higher-order-functions-i-function-composition-and-the-monad-pattern-bc74a?utm_source=blog&utm_medium=blog&utm_campaign=j.kaplan</a></span><br />
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></div>
<div>
<h2>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Conclusion</span></h2>
</div>
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></div>
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Typed languages (including Java) you should use Either.</span></div>
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;">Dynamic languages the best would be pipeline with exceptions.</span></div>
<div>
<br /></div>
</div>
<div>
<span style="font-family: "helvetica neue" , "arial" , "helvetica" , sans-serif;"><br /></span></div>
<br /><div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-71409960629679958622019-09-10T22:21:00.000+03:002019-09-12T16:09:24.945+03:00How to solve it? Crafting web apps using finite state machines - Part I<h2>
</h2>
Code: <a href="https://gitlab.com/danbunea/how-to-solve-it-crafting-web-apps">https://gitlab.com/danbunea/how-to-solve-it-crafting-web-apps</a><br />
Presentation: <a href="http://www.danbunea.ro/presentations/crafting%20web%20apps.html">http://www.danbunea.ro/presentations/crafting%20web%20apps.html</a><br />
<!--iframe allowfullscreen="" frameborder="1" height="700px" marginheight="0px" marginwidth="0px" name="myiFrame" scrolling="yes" src="https://www.danbunea.ro/presentations/crafting%20web%20apps.html" style="border: 0px #ffffff none;" width="800px"></iframe-->
<br />
<h2>
</h2>
<h2>
PROBLEM</h2>
<div>
<br /></div>
<h2>
How should you start crafting a web app?</h2>
<br />
The requirements come, you have some UI mockups and now you need to start developing a web app. What is the first step you need to do?<br />
<br />
Let's go back a bit. If this was not a specific web app, but rather generic:<br />
<br />
<ul>
<li>What would our strategy be like?</li>
<li>What would be our goals?</li>
<li>Which steps would we have to do?</li>
<li>Which patterns could help us?</li>
<li>What tactics could we use?</li>
</ul>
<br />
In the following lines I will try to address exactly this questions. First we'll discuss the theory and then we'll develop a web app.<br />
<br />
we'll start with a little bit of theory, then we'll put it in practice:<br />
<br />
<br />
<br />
<h2>
THEORY </h2>
<h2>
</h2>
<h2>
</h2>
<h2>
Strategy</h2>
<div>
<br /></div>
We'll have to think a bit strategically and a little bit tactically. For the strategy, what would be our goals:<br />
<h4>
</h4>
<h4>
Goals</h4>
<ul>
<li>Break the complexity down (state machines, separation of concerns)</li>
<li>Build robustness into the system (atomicity with rollback)</li>
</ul>
Ok, but how can this be done?<br />
<br />
Well, we'll do it in<b> 4 Steps</b><br />
<ol>
<li>Finite state machine diagram (state/transitions starting from the UI)</li>
<li>States (domain modelling for the states)</li>
<li>Transitions (TDD the controller)</li>
<li>Presentation/UI for the states (TDD the view)</li>
</ol>
<br />
Steps don't need to be sequential, in fact it is recommended to do the last two in parallel.<br />
<br />
<h3>
<b>Tactics</b></h3>
<br />
We will be using a few <b>Patterns</b>:<br />
<ul>
<li>finite state machines</li>
<li>separation of concerns using MVC</li>
<li>atomicity = all or nothing</li>
</ul>
and a few <b>Techniques</b>:<br />
<ul>
<li>TDD - for the non UI</li>
<li>TDD - for the UI</li>
<li>Design by contract</li>
</ul>
<h2>
</h2>
<h2>
</h2>
<h2>
PRACTICE</h2>
<br />
TodoMVC web application, because everyone loves to prove their framework with it, but we'll focus on what we said before.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinOekYWyEFqJTiiRDsfSXffXHRttEOmWJxNGkUUo6lEetnhBG06N54vZC93ylte66wlerbs9-z6JdXzWGLj9UHZApLhNezGXje_2a_fuEvJsfnrWUkOcQ8TYfRqUg0M1l9ni42/s1600/68747470733a2f2f6a2e676966732e636f6d2f4b316d6e52472e676966.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1028" data-original-width="894" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinOekYWyEFqJTiiRDsfSXffXHRttEOmWJxNGkUUo6lEetnhBG06N54vZC93ylte66wlerbs9-z6JdXzWGLj9UHZApLhNezGXje_2a_fuEvJsfnrWUkOcQ8TYfRqUg0M1l9ni42/s400/68747470733a2f2f6a2e676966732e636f6d2f4b316d6e52472e676966.gif" width="347" /></a></div>
<br />
<br />
<h3>
Step 1 Draw the diagram of the UI state machine</h3>
<br />
So let's start with Step 1 Finite state machine<br />
<br />
Our purpose is to create a state diagram with all the states and transitions we think we'll need. This is not an easy step, but it can clarify the entire development process further on. Let's see what can be done<br />
<br />
Actions:<br />
<ul>
<li>list</li>
<li>filter</li>
<li>add</li>
<li>check/unckeck</li>
<li>edit</li>
<li>delete</li>
</ul>
From the screens it also look like we'll have the following States:<br />
<ul>
<li>list (filtered or not)</li>
<li>add</li>
<li>edit</li>
</ul>
and now let's put it in a diagram:<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVJVVepWIEyEnHkr9vjOSd-9l5Mxq42K84pR3ajtdxEW99YHjGN3CpHEaGISED7Z55dZAYD0pCdAOOvu9b7Qr6Ud8wBDRE2a-ZX1W8bVPkHTyRmXhbr5FnmraeOFW9Fq7Lc_P8/s1600/state+diagram.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1016" data-original-width="1258" height="322" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVJVVepWIEyEnHkr9vjOSd-9l5Mxq42K84pR3ajtdxEW99YHjGN3CpHEaGISED7Z55dZAYD0pCdAOOvu9b7Qr6Ud8wBDRE2a-ZX1W8bVPkHTyRmXhbr5FnmraeOFW9Fq7Lc_P8/s400/state+diagram.png" width="400" /></a></div>
<br />
<br />
or:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYdiCoXcxc6lUawkFZq8reb3DZ7e9Ih1_XrnFIe3j-vrsU2_TqUGqzfHTLeh5QMcUWkmwl6YpK4ltGn2euETYKH003n7UlCEIk40BWoL2lCl1nhelGserfVlpiLxe89IqudnP2/s1600/state+diagram+screens.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1016" data-original-width="1258" height="321" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYdiCoXcxc6lUawkFZq8reb3DZ7e9Ih1_XrnFIe3j-vrsU2_TqUGqzfHTLeh5QMcUWkmwl6YpK4ltGn2euETYKH003n7UlCEIk40BWoL2lCl1nhelGserfVlpiLxe89IqudnP2/s400/state+diagram+screens.png" width="400" /></a></div>
<br />
<br />
Now using theis diagram we'll move to step 2 and we'll do the code according to it.<br />
<br />
<br />
<h3>
Step 2: Domain modelling</h3>
<br />
<br />
We will separate the different concerns using the MVC pattern. All the logic will be in the controller, the data in the model and we'll add the presentation later or someone else could do it in parallel.<br />
<br />
Our data will have to be able to represent all the states in the diagram above: list/filter, add and edit<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: "andale mono" , "lucida console" , "monaco" , "fixed" , monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code><u>;What is a todo?</u>
{:text "todo", :done? true}
<u>;How do we know the status?
;When do we filter?</u>
{:context {:status "list"
:filter true}
:todos [{:text "todo 1" :done? true}
{:text "todo 2" :done? false}]}
<u>;What about edit?
;How do we know which do we edit?
;How do we order the todos?</u>
{:context {:status "edit"
:filter true
:selected-id 1},
:todos {
:1 {:id 1, :text "todo 1", :done? true, :time 1}
:2 {:id 2, :text "todo 2", :done? false, :time 2}
}}
<u>;Let's carve it in stone:
</u>
(require '[clojure.spec.alpha :as s])
;SPEC
(s/def ::status #{"list" "edit" "add"})
(s/def ::filter #(or (false? %) (true? %)))
(s/def ::filter-with-nil #(or (nil? %) (false? %) (true? %)))
(s/def ::selected-id nat-int?)
(s/def ::context (s/keys :req-un [::status] :opt-un [::filter-id ::selected-id]))
(s/def ::id nat-int?)
(s/def ::text string?)
(s/def ::done? boolean?)
(s/def ::time number?)
(s/def ::todo (s/keys :req-un [::id ::text ::done? ::time]))
(s/def ::todos (s/map-of keyword? ::todo))
(defn edit-mode-has-a-valid-selected-id? [state]
(if (= "edit" (get-in state [:context :status]))
(some (into #{} (map :id (vals (:todos state)))) [(get-in state [:context :selected-id])])
true
))
(s/def ::model (s/and
(s/keys :req-un [::context] :opt-un [::todos])
edit-mode-has-a-valid-selected-id?
))
(comment
(s/valid? ::model {:context {:status "edit"
:filter true
:selected-id 1}
:todos {
:1 {:id 1, :text "todo 1", :done? true, :time 1}
:2 {:id 2, :text "todo 2", :done? false, :time 2}
}}))
</code></pre>
<br />
<h3>
Step 3: Solve the data problem</h3>
<br />
Now we know how we could represent our states as data in the model, let's test drive the different transitions which will al be functions in the controller. Normally I start with a plan, where I will know what I want to test.<br />
<br />
The plan of the tests<br />
<br />
[ ] controller-should<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>initialize-in-list-mode<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>check&uncheck<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>delete<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>toggle-filters<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>set a filter<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>remove all filters<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>set-add-mode<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>save-a-new-todo<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>set-list-mode<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>set-one-for-edit<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>save-a-changed-todo<br />
<br />
Then we'll write the first one:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); font-family: "Andale Mono", "Lucida Console", Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 410px;"><code>(deftest initialize-in-list-mode
(is (= {:context {:status "list"} :todos {}}
(init!))))</code></pre>
<br />
which will obviously<br />
<br />
Test fails! Good... now we write the code to make it pass:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); font-family: "Andale Mono", "Lucida Console", Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 410px;"><code>;THE MODEL
(def model (atom {:context {:status "list"}}))
;THE CONTROLLER
(defn commit! [value atom-to-change]
(reset! atom-to-change value))
(defn init! []
(-> @model
(assoc :todos {})
(assoc-in [:context :status] "list")
(commit! model)))
;THE TEST
(deftest initialize-in-list-mode
(is (= {:context {:status "list"} :todos {}}
(init!))))</code></pre>
<br />
<br />
Or:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="500" src="https://www.youtube.com/embed/CvEoXuoAhCg?controls=0" width="800"></iframe>
</div>
<br />
Any refactorings? No, let's move to the second test and so on until we solve the entire data problem, one test at a time, making sure it fails, then making it pass, then refactoring the code. We end up with this:<br />
<br />
The code is merged here:<br />
<a href="https://gitlab.com/danbunea/how-to-solve-it-crafting-web-apps/merge_requests/1/diffs">https://gitlab.com/danbunea/how-to-solve-it-crafting-web-apps/merge_requests/1/diffs</a><br />
<br />
The final code:<br />
<a href="https://gitlab.com/danbunea/how-to-solve-it-crafting-web-apps/blob/master/test/todomvc/controller_should.cljs">https://gitlab.com/danbunea/how-to-solve-it-crafting-web-apps/blob/master/test/todomvc/controller_should.cljs</a><br />
<a href="https://gitlab.com/danbunea/how-to-solve-it-crafting-web-apps/blob/master/src/todomvc/controller.cljs">https://gitlab.com/danbunea/how-to-solve-it-crafting-web-apps/blob/master/src/todomvc/controller.cljs</a><br />
<br />
You'll probably be able to see that the names of the functions in our controller closely follow our transitions from the diagram.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhQUHiUhMMh0bqnjQUKQihN_GDzFlRKfeXjk-ADqF26ij13oW8ItLMKUoY6VZHURlefTMFybRXyI1s9SnBDDstVWBrAKi98isPsd8ysoCNMlJisugD-JuPD4jtXLgaOEZ44fbk/s1600/controller+tests.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1195" data-original-width="1600" height="475" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhQUHiUhMMh0bqnjQUKQihN_GDzFlRKfeXjk-ADqF26ij13oW8ItLMKUoY6VZHURlefTMFybRXyI1s9SnBDDstVWBrAKi98isPsd8ysoCNMlJisugD-JuPD4jtXLgaOEZ44fbk/s640/controller+tests.png" width="640" /></a></div>
<br />
<br />
Solve the data problem (TDD the model/controller) Done!<br />
<br />
Now let's have another look again to see the correspondence between the transitions and the controller functions:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4_Ep043wBrM-yfNS2Dn0SHC_mWtoCb79nZ0OL2bRF0QlhXAirxcOth3wMcmQ7RCxoJ6s0S_2qkFUxNRRV8L36l8Kqa68UqFvhVzCTQCnhUcCKn8f2TIAhc0WSwBYFLpBPMKcW/s1600/controller+to+diagram.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1048" data-original-width="1600" height="418" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4_Ep043wBrM-yfNS2Dn0SHC_mWtoCb79nZ0OL2bRF0QlhXAirxcOth3wMcmQ7RCxoJ6s0S_2qkFUxNRRV8L36l8Kqa68UqFvhVzCTQCnhUcCKn8f2TIAhc0WSwBYFLpBPMKcW/s640/controller+to+diagram.png" width="640" /></a></div>
<br />
<div>
<br /></div>
while the data in the model corresponds to:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhL7QwQHGzNNf0hjdRCUx9A41eRDC3uvgf7P2qREPx_xf3QqDWLHBUjGUXxPyHgCTjCs9EbiP1c6zy8Iu65-FckPHbkzSjQQKhwQK7cwznVrJCO1m-_ywI48qx3RE_z3Gch0q6e/s1600/model+to+diagram.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="661" data-original-width="1600" height="264" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhL7QwQHGzNNf0hjdRCUx9A41eRDC3uvgf7P2qREPx_xf3QqDWLHBUjGUXxPyHgCTjCs9EbiP1c6zy8Iu65-FckPHbkzSjQQKhwQK7cwznVrJCO1m-_ywI48qx3RE_z3Gch0q6e/s640/model+to+diagram.png" width="640" /></a></div>
<br />
<br />
<h3>
</h3>
<h3>
Step 4: Build the UI</h3>
<br />
We will need now to make sure that our states can be represented on the screen.<br />
<br />
For instance when we're in list mode, filtered:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkFN3cxDkoAGZz5vkOH67qo4N7jRa-eoR6Lw7GbDAqPc6_5ddRpwFTpW4NG5q0gUdH7j5gpFOyoLgkmvWkl4zqrXzPsXpo_2vZzYL-r0up0ux6CAurkKmE5okYYmNTgqDxWRZb/s1600/filtered+list+model+to+view.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="699" data-original-width="1600" height="278" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkFN3cxDkoAGZz5vkOH67qo4N7jRa-eoR6Lw7GbDAqPc6_5ddRpwFTpW4NG5q0gUdH7j5gpFOyoLgkmvWkl4zqrXzPsXpo_2vZzYL-r0up0ux6CAurkKmE5okYYmNTgqDxWRZb/s640/filtered+list+model+to+view.png" width="640" /></a></div>
<br />
<br />
How about edit state:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigwG9qetZuFIiyE9WDDPIeTggtXuUNEvsyOPBsYggOPU-QxGyjiL-3RGlf60Aem_O5y1Sj8ApWSseEjJK_hTNacu56uipo_SuUlZ2GJowxrdckCfZE8rI5hbZwHXCAFLcRJpbu/s1600/edit+todo+model+to+view.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="734" data-original-width="1600" height="292" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigwG9qetZuFIiyE9WDDPIeTggtXuUNEvsyOPBsYggOPU-QxGyjiL-3RGlf60Aem_O5y1Sj8ApWSseEjJK_hTNacu56uipo_SuUlZ2GJowxrdckCfZE8rI5hbZwHXCAFLcRJpbu/s640/edit+todo+model+to+view.png" width="640" /></a></div>
<br />
<br />
Now that we know how the data corresponds to the UI,<br />
we can move on to doing the actual UI. We'll use react/reagent to break the UI into components:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmEXW-hZv4bLVeB2t1-W88aTz0MaIIfTeqM0cMvZUDyc_TEYRq-N-fYvZO4GK5TdF7iJ81XbADk6mO9kNpnFttRvt62T0PmYZ-gYY6ujo22hSoGxBytjvOjAPWrvf-zhZoRuUA/s1600/component+hierarchy+and+UI+relations.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="854" data-original-width="1600" height="339" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmEXW-hZv4bLVeB2t1-W88aTz0MaIIfTeqM0cMvZUDyc_TEYRq-N-fYvZO4GK5TdF7iJ81XbADk6mO9kNpnFttRvt62T0PmYZ-gYY6ujo22hSoGxBytjvOjAPWrvf-zhZoRuUA/s640/component+hierarchy+and+UI+relations.png" width="640" /></a></div>
<br />
Then we'll test drive the entire UI starting by planning the tests:<br />
<br />
[ ] views_should<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>ender-the-main-screen<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>render all sections<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>not render main section when no todos<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>render-input-text-component<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>use-keys-on-input-text-component<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>write something and hit enter<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>hitting enter with no text<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>hitting esc<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>render todo input component<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>add-a-new-todo-when-clicking-enter-or-go-to-list-on-escape<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>render-todos-list-component<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>no filter<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>filter active<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>filter completed<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>render-todo-item-component<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>normal mode<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>completed mode<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>editing mode<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>toggle a todo item component<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>render-todos-count-component<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>no item left<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>1 item left<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>2 items left<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>render-todos-filters-component<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>render no filter<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>render filter active<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>render filter completed<br />
[ ] <span class="Apple-tab-span" style="white-space: pre;"> </span>invoke-controller-filter-when-clicking-filters-in-todos-filters-component<br />
<br />
It is worth mentioning that TDD-ing the UI can be split into two types of tests:<br />
- render tests and<br />
- interaction tests<br />
<br />
For the render tests you send some data to your components, render it then check how it's rendered.<br />
We start with a render test (we'll use enzyme):<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); font-family: "Andale Mono", "Lucida Console", Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 410px;"><code>(deftest render-the-main-screen
(testing "render all sections"
(let [component [views/screen-component data/one-todo-list-mode-no-filter]
mounted (->> (r/as-element component)
(.mount js/enzyme))]
(is (= 1 (-> mounted (.find ".header") .-length)))
(is (= 1 (-> mounted (.find ".main") .-length)))
(is (= 1 (-> mounted (.find ".footer") .-length)))
(is (= 1 (-> mounted (.find "li.todo") .-length)))
(is (= "0 items left" (-> mounted (.find "strong") .getDOMNode .-innerText)))
)))</code></pre>
<br />
the test fails, so then we start writing our first component, making it pass, refactoring etc.<br />
<br />
For the interaction tests, you render some data, then click a button and make sure the mocked function that should be invoked is actually invoked:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); font-family: "Andale Mono", "Lucida Console", Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 410px;"><code>(deftest invoke-controller-filter-when-clicking-filters-in-todos-filters-component
(let [invocations (atom [])
component [views/todos-filters-component nil]
mounted (->> (r/as-element component)
(.mount js/enzyme))]
(with-redefs [controller/filter! #(swap! invocations conj [%])]
(testing "unfilter"
(reset! invocations [])
(-> mounted
(.find "#all")
(.simulate "click"))
(is (= [[nil]] @invocations)))
))</code></pre>
<br />
The code is merged here:<br />
<a href="https://gitlab.com/danbunea/how-to-solve-it-crafting-web-apps/merge_requests/2/diffs">https://gitlab.com/danbunea/how-to-solve-it-crafting-web-apps/merge_requests/2/diffs</a><br />
<br />
The final code:<br />
<a href="https://gitlab.com/danbunea/how-to-solve-it-crafting-web-apps/blob/master/test/todomvc/views_should.cljs">https://gitlab.com/danbunea/how-to-solve-it-crafting-web-apps/blob/master/test/todomvc/views_should.cljs</a><br />
<a href="https://gitlab.com/danbunea/how-to-solve-it-crafting-web-apps/blob/master/src/todomvc/views.cljs">https://gitlab.com/danbunea/how-to-solve-it-crafting-web-apps/blob/master/src/todomvc/views.cljs</a><br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhy21Mo2zjk2jjhNVG9-SRy-1JWmNrKxEoWznYDth0QzYNeXf7oNzdeXJa2kmHdErTvm0XHX78Niv7_j4FOUxEVOwBKIO5nN9UWvMNdQsFGUFo0HCo4f_gKz6936SBmJDjy1naC/s1600/view+tests.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1050" data-original-width="1600" height="419" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhy21Mo2zjk2jjhNVG9-SRy-1JWmNrKxEoWznYDth0QzYNeXf7oNzdeXJa2kmHdErTvm0XHX78Niv7_j4FOUxEVOwBKIO5nN9UWvMNdQsFGUFo0HCo4f_gKz6936SBmJDjy1naC/s640/view+tests.png" width="640" /></a></div>
<br />
<br />
<h2>
Goals revised</h2>
<br />
Complexity problem -solved by making a state machine and using separation of concerns.<br />
Robustness problem - TDD is a clear step forward but we can go further:<br />
<br />
<ul>
<li>add atomicity into the transitions/operations (all or nothing)</li>
<li>design by contract, always making sure the states are valid states</li>
</ul>
<div>
But we'll see that in part II</div>
<br />
<br /><div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-7794007629819401212019-07-22T12:43:00.001+03:002019-07-22T13:24:17.953+03:00Thinking in Reagent as a reaction to Thinking in React. A 1:1 transformation from Javascript/React to ClojureScript/ReagentThis article: <a href="https://reactjs.org/docs/thinking-in-react.html">https://reactjs.org/docs/thinking-in-react.html</a> introduces people to working with React in Javascript. I thought it would be nice to have the same simple application made in Clojurescript with Reagent (which also uses React underneath)<br />
<br />
For example<br />
<br />
<div style="font-family: "Helvetica Neue"; font-size: 12px; font-stretch: normal; line-height: normal;">
class ProductCategoryRow extends React.Component {</div>
<div style="font-family: "Helvetica Neue"; font-size: 12px; font-stretch: normal; line-height: normal;">
render() {</div>
<div style="font-family: "Helvetica Neue"; font-size: 12px; font-stretch: normal; line-height: normal;">
const category = this.props.category;</div>
<div style="font-family: "Helvetica Neue"; font-size: 12px; font-stretch: normal; line-height: normal;">
return (</div>
<div style="font-family: "Helvetica Neue"; font-size: 12px; font-stretch: normal; line-height: normal;">
</div>
<div style="font-family: "Helvetica Neue"; font-size: 12px; font-stretch: normal; line-height: normal;">
</div>
<div style="font-family: "Helvetica Neue"; font-size: 12px; font-stretch: normal; line-height: normal;">
{category}</div>
<div style="font-family: "Helvetica Neue"; font-size: 12px; font-stretch: normal; line-height: normal;">
</div>
<div style="font-family: "Helvetica Neue"; font-size: 12px; font-stretch: normal; line-height: normal;">
</div>
<div style="font-family: "Helvetica Neue"; font-size: 12px; font-stretch: normal; line-height: normal;">
);</div>
<div style="font-family: "Helvetica Neue"; font-size: 12px; font-stretch: normal; line-height: normal;">
}</div>
<br />
<div style="font-family: "Helvetica Neue"; font-size: 12px; font-stretch: normal; line-height: normal;">
}</div>
<div style="font-family: "Helvetica Neue"; font-size: 12px; font-stretch: normal; line-height: normal;">
<br /></div>
<div style="font-family: "Helvetica Neue"; font-size: 12px; font-stretch: normal; line-height: normal;">
<br /></div>
In Reagent:<br />
<br />
(defn product-category-row-component [category]<br />
[:tr<br />
[:th<br />
{:col-span 2}<br />
category]])<br />
<br />
<br />
I made an interactive presentation and I put it here:<br />
<br />
<a href="http://www.danbunea.ro/presentations/thinking%20in%20reagent.html">http://www.danbunea.ro/presentations/thinking%20in%20reagent.html</a><br />
<br />
<br />
<br />
<br />
<iframe allowfullscreen="" frameborder="1" height="700px" marginheight="0px" marginwidth="0px" name="myiFrame" scrolling="yes" src="https://www.danbunea.ro/presentations/thinking%20in%20reagent.html" style="border: 0px #ffffff none;" width="800px"></iframe>
<div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-46456816693378080502018-09-21T13:38:00.000+03:002018-09-21T13:38:52.822+03:00Outside-In TDD for the functional python server with Flask and SqlAlchemy<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Code: <a href="https://github.com/DanBunea/python_functional_server">https://github.com/DanBunea/python_functional_server</a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
In the following video, you will see a graph and how in time we expand the code using the Outside-in TDD </div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<iframe width="320" height="266" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/AsOY0IdHiL0/0.jpg" src="https://www.youtube.com/embed/AsOY0IdHiL0?feature=player_embedded" frameborder="0" allowfullscreen></iframe></div>
<h3>
Requirements</h3>
<br />
We have an web API where we should be able to write and read articles and their comments.<br />
<br />
Acceptance Test<br />
<br />
We save an article:<br />
<br />
POST /api/1/save/Article<br />
{<br />
"title":"New article",<br />
"content":"The content"<br />
}<br />
and we receive the article + id<br />
{<br />
id: 13, - can be any value<br />
title:"New article",<br />
content:"The content"<br />
}<br />
We then add 2 new comments:<br />
POST /api/1/save/Article<br />
{<br />
id: 13, - can be any value<br />
comments:[<br />
{"comment":"This was awesome!"},<br />
{"comment":"I loved it as well!"},<br />
]<br />
}<br />
response:<br />
{<br />
id: 13, - can be any value<br />
title:"New article",<br />
content:"The content"<br />
}<br />
and now reading the articles and the comments:<br />
POST api/1/query/Article<br />
{<br />
find: [id,title,content,{comments:[id,comment]}],<br />
where:{id:13}<br />
}<br />
should return<br />
{"13":{<br />
<br />
id: 13, - can be any value<br />
comments:{<br />
"113":{"comment":"This was awesome!"},<br />
"114":{"comment":"I loved it as well!"},<br />
}<br />
}<br />
}<br />
<br />
<h2>
Outside-In TDD approach</h2>
<br />
Unlike classicist TDD, in Outside-In, you start the code from the outside, designing it along the way. You start with an acceptance test, then with the unit/integration tests and code needed until you make the acceptance test work:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1dUJcqMu3XqiLebQHbVVxMJbQVkxoeokfB9RE4gSoqNcgZGpL5xFllDBsLCUcv2fZG9qL-DWg1CaKyQpfegg34tkGkSQnCIVaiCFyst3iTJ-0GHTd4RM5kglXYUTfnm_TC3ER/s1600/tdd-double-loop.png" imageanchor="1"><img border="0" height="220" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1dUJcqMu3XqiLebQHbVVxMJbQVkxoeokfB9RE4gSoqNcgZGpL5xFllDBsLCUcv2fZG9qL-DWg1CaKyQpfegg34tkGkSQnCIVaiCFyst3iTJ-0GHTd4RM5kglXYUTfnm_TC3ER/s320/tdd-double-loop.png" width="320" /></a><br />
<br />
<br />
So let's begin:<br />
<br />
<h3>
Step 1: Acceptance test</h3>
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipk5-T2obTqhb0j3-YwRamK_xvm15Pks7cY5adUNiyySWOd9VVhZqRK1gzTyVyfSmlH49Qr6aYsw2gIKGAFfuaswpaPdpcsXfLTtNsCqP1Ra-oIRjVjqtWNjFvU8wLdo1ZuhhB/s1600/step+1.png" imageanchor="1"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipk5-T2obTqhb0j3-YwRamK_xvm15Pks7cY5adUNiyySWOd9VVhZqRK1gzTyVyfSmlH49Qr6aYsw2gIKGAFfuaswpaPdpcsXfLTtNsCqP1Ra-oIRjVjqtWNjFvU8wLdo1ZuhhB/s640/step+1.png" width="640" /></a><br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhU0MZz-MAJ6fal3lLx8r56qzGwi6j4pZDT9rxRbOy2bIftiialVz0KGOoyVKDz5qYcQXa8y93g7zuwa4hqCv9VebhkfiGzoVxnHSilw3e8Jcvx7WHI24oo44H2S3nboGnWg9d4/s1600/initial.png" imageanchor="1"><img border="0" height="455" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhU0MZz-MAJ6fal3lLx8r56qzGwi6j4pZDT9rxRbOy2bIftiialVz0KGOoyVKDz5qYcQXa8y93g7zuwa4hqCv9VebhkfiGzoVxnHSilw3e8Jcvx7WHI24oo44H2S3nboGnWg9d4/s640/initial.png" width="640" /></a><br />
<br />
When we run it:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifivZF0freDgkbVjRUAYoPCV_RMLKpOe2h0yi9ZxYEoGgNViWkXe_L9tz6kpmv8_xAXXbSaImmrCPIH73KH26pybbHm_LUeNu7HUFt8Jfs3kPCtXkD1XfC-WXdXa4ZirO1cjPU/s1600/no+route+found.png" imageanchor="1"><img border="0" height="94" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifivZF0freDgkbVjRUAYoPCV_RMLKpOe2h0yi9ZxYEoGgNViWkXe_L9tz6kpmv8_xAXXbSaImmrCPIH73KH26pybbHm_LUeNu7HUFt8Jfs3kPCtXkD1XfC-WXdXa4ZirO1cjPU/s640/no+route+found.png" width="640" /></a><br />
<br />
<br />
<h3>
2. Step 2 moving in, </h3>
<br />
From the outside, writing the first test for the service that will handle the url. A little bit of design, while writing the test: we consider, that we will use a function that will get the object from the database if we have an id in the json or will give us a brand new Article object if we have no id in the json.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPXBKoc8_WbkQnytQElNx40nHvPji0oXYbQKTmUPCwyUOttR8ggUnOWJCRkPcU6qlNLASp4g3fn1CeS1crPAiuA8TfuCLbiu4HaBFFNzs9aYDotZ6Mgf7QgFBG1PqpNIJU4uNS/s1600/step+2.png" imageanchor="1"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPXBKoc8_WbkQnytQElNx40nHvPji0oXYbQKTmUPCwyUOttR8ggUnOWJCRkPcU6qlNLASp4g3fn1CeS1crPAiuA8TfuCLbiu4HaBFFNzs9aYDotZ6Mgf7QgFBG1PqpNIJU4uNS/s640/step+2.png" width="640" /></a><br />
<br />
<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYdaDMxV_2Xriq5Ce98msoD4uZUxBMgO8YQuDWJHmVs1rIcG7Rl0vdg2EPz5oeUn8vN47GHHvIhf54ARrByi5azEPhVaq2Q-Xudlp3JighYtDfBhOBj04KDejXWiAJKcuvWJWy/s1600/Screen+Shot+2018-09-21+at+11.16.58.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYdaDMxV_2Xriq5Ce98msoD4uZUxBMgO8YQuDWJHmVs1rIcG7Rl0vdg2EPz5oeUn8vN47GHHvIhf54ARrByi5azEPhVaq2Q-Xudlp3JighYtDfBhOBj04KDejXWiAJKcuvWJWy/s1600/Screen+Shot+2018-09-21+at+11.16.58.png" /></a><br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0ru86Y2rvV0YHnGcelBGBnCot2VPk1qMfjCR7zdZdPNeXQPNpzpkhhIiF1WBqasSnEV3T9e0JOrBnKxunAajaqs5ogvlb7uKXifx19N0Uw7nCREbQNp6us6qKkMIJC4rOCaeq/s1600/Screen+Shot+2018-09-21+at+11.16.45.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0ru86Y2rvV0YHnGcelBGBnCot2VPk1qMfjCR7zdZdPNeXQPNpzpkhhIiF1WBqasSnEV3T9e0JOrBnKxunAajaqs5ogvlb7uKXifx19N0Uw7nCREbQNp6us6qKkMIJC4rOCaeq/s1600/Screen+Shot+2018-09-21+at+11.16.45.png" /></a><br />
<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5mnIIDahnt0DoW3wKnO4f8AaV9LWDHhs9qQ9AaMlppS7BxRKg4PQ2GtTvAlm108NVqzHMTG2MMsmUNsusnNIG6jgH7ulxwyPAVhAY8dzmYF5Yxi5wjmBYvOIhL_XUts7cr644/s1600/Screen+Shot+2018-09-21+at+11.16.45.png" imageanchor="1"></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFlO4B8zO7wKluSj1VoFq_vwUI5zFcTZlR8cz1j7bVzMBEirqr2eQ6Kql7l0zJDsG9sPyKRctebYEo-0z2-MhERh6GVJ5jLFLYME5wJEt15uMTt5Ch6cLQCmb4bZLK5_HT3YmN/s1600/Screen+Shot+2018-09-21+at+11.17.09.png" imageanchor="1"><img border="0" height="194" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFlO4B8zO7wKluSj1VoFq_vwUI5zFcTZlR8cz1j7bVzMBEirqr2eQ6Kql7l0zJDsG9sPyKRctebYEo-0z2-MhERh6GVJ5jLFLYME5wJEt15uMTt5Ch6cLQCmb4bZLK5_HT3YmN/s640/Screen+Shot+2018-09-21+at+11.17.09.png" width="640" /></a><br />
<br />
<br />
Now if we properly do the code, the test will pass:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEip1mFlgwgVrkt7dzUAqWZBp7TLo89rgPUilJkRlFY49luv4bpxesVdpNQO5AxevK_wGjXive9BOeFk-3VHOgVcW08tK1pziK7j82m-7khMhsXMbPUi15wN6FNkpQZdNnbHmXkZ/s1600/step+2+apitest+green.png" imageanchor="1"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEip1mFlgwgVrkt7dzUAqWZBp7TLo89rgPUilJkRlFY49luv4bpxesVdpNQO5AxevK_wGjXive9BOeFk-3VHOgVcW08tK1pziK7j82m-7khMhsXMbPUi15wN6FNkpQZdNnbHmXkZ/s640/step+2+apitest+green.png" width="640" /></a><br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpPgEmZkLeipoc4dUQC6JIQq9gPPP4pCnkLI2biH9Oybbx2VXxb5QZCOxV4mk7T45tgWrIj9sgQEaVwJamdRege7AhN0kGlyrovf2FsHJm1dIhvQkXQ7uGP6WcOMebtcWYYhgs/s1600/Screen+Shot+2018-09-21+at+11.18.09.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpPgEmZkLeipoc4dUQC6JIQq9gPPP4pCnkLI2biH9Oybbx2VXxb5QZCOxV4mk7T45tgWrIj9sgQEaVwJamdRege7AhN0kGlyrovf2FsHJm1dIhvQkXQ7uGP6WcOMebtcWYYhgs/s1600/Screen+Shot+2018-09-21+at+11.18.09.png" /></a><br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguglkbpt1iG-vEOWgkKtI5XizjcqSFGNnlKGLKnJaJONxx-J0JB9ECj835BX5GklYd2iksjQhn-BkKacOlFAIAtD6VXZ_eRTdjqDPj1wslHOWXsCAToLb1UsIGIlrktNw9mOXj/s1600/Screen+Shot+2018-09-21+at+11.18.19.png" imageanchor="1"><img border="0" height="132" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguglkbpt1iG-vEOWgkKtI5XizjcqSFGNnlKGLKnJaJONxx-J0JB9ECj835BX5GklYd2iksjQhn-BkKacOlFAIAtD6VXZ_eRTdjqDPj1wslHOWXsCAToLb1UsIGIlrktNw9mOXj/s320/Screen+Shot+2018-09-21+at+11.18.19.png" width="320" /></a><br />
<br />
<br />
<h3>
Step 3: moving furher in, database_services and get_database_object</h3>
<br />
However, we did not implement the get_database_object, which will work with the database. So for that we'll write the tests first:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigmj7aJIs_MKEsZt2m4S5mXyVJWdeUwXcZ3FGiO4sn1AOmV7wkiOg5T3aSl3Pwih3MbgbCTYq8Y5Nn1N5W-ApHlSvpOPFVpMeV8P35EvyXxCitKkESiblU_MSW4-hK3kdOmH6w/s1600/step+4+dbtest+red.png" imageanchor="1"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigmj7aJIs_MKEsZt2m4S5mXyVJWdeUwXcZ3FGiO4sn1AOmV7wkiOg5T3aSl3Pwih3MbgbCTYq8Y5Nn1N5W-ApHlSvpOPFVpMeV8P35EvyXxCitKkESiblU_MSW4-hK3kdOmH6w/s640/step+4+dbtest+red.png" width="640" /></a><br />
or:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguT5c_TOebuM4aZZdO5KGPR9HtK3Vu-6v-EU5GS2GEIKgRy5qL5La3TTWFT5vXIrRsAtFmHvkxCYVgShDCsAhVWy6Xg8tA294muHrHIEhKfbWH4LRGCZCrXvaY4DVxwvl-Q0JL/s1600/Screen+Shot+2018-09-21+at+11.21.46.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguT5c_TOebuM4aZZdO5KGPR9HtK3Vu-6v-EU5GS2GEIKgRy5qL5La3TTWFT5vXIrRsAtFmHvkxCYVgShDCsAhVWy6Xg8tA294muHrHIEhKfbWH4LRGCZCrXvaY4DVxwvl-Q0JL/s1600/Screen+Shot+2018-09-21+at+11.21.46.png" /></a><br />
<br />
and<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiuAw4TV0VxZEKblLiiKedRf2Sqj_1XDOhR4cnKiZ9KIBCax3RjoMmN-fSkelbZknv0o3macYlHewd2HAFcPLADwb10aH41GVZcRm0SlSLVs7CXXJf0R5xUgwAsV9SMJHry3fm/s1600/Screen+Shot+2018-09-21+at+11.22.06.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiuAw4TV0VxZEKblLiiKedRf2Sqj_1XDOhR4cnKiZ9KIBCax3RjoMmN-fSkelbZknv0o3macYlHewd2HAFcPLADwb10aH41GVZcRm0SlSLVs7CXXJf0R5xUgwAsV9SMJHry3fm/s1600/Screen+Shot+2018-09-21+at+11.22.06.png" /></a><br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7RWmBSsBMY0x5yB95FTVbPRfjRBj-0mooCqz_HQaOOuMssPsoVueqMWWHQfJ_DweCnBYSWUvGCeuhJiA5b_89XFwVPij4sG3bxaMJ3VeCDQh-lrN_4TMd1r9PMEOIWz6dS-sl/s1600/Screen+Shot+2018-09-21+at+11.22.15.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7RWmBSsBMY0x5yB95FTVbPRfjRBj-0mooCqz_HQaOOuMssPsoVueqMWWHQfJ_DweCnBYSWUvGCeuhJiA5b_89XFwVPij4sG3bxaMJ3VeCDQh-lrN_4TMd1r9PMEOIWz6dS-sl/s1600/Screen+Shot+2018-09-21+at+11.22.15.png" /></a><br />
<br />
<br />
Now let's write the code to make it pass:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkFX_E9T7JBqrItx689WSmORONr8dc5F28bjsPRAq0EiClz-kXmnZAiGjBZorcU9VA5630aOPZk9TEB-YxYMojZ9OSERvrCpV_9uCRwd9awoKA1IOMOXXhHF48Tv1qIvuL5NuY/s1600/step+5+db+services+test+green.png" imageanchor="1"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkFX_E9T7JBqrItx689WSmORONr8dc5F28bjsPRAq0EiClz-kXmnZAiGjBZorcU9VA5630aOPZk9TEB-YxYMojZ9OSERvrCpV_9uCRwd9awoKA1IOMOXXhHF48Tv1qIvuL5NuY/s640/step+5+db+services+test+green.png" width="640" /></a><br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhY9QwSxoVzO62M9PyWM_Z2gdnO5j4w1Rtn8M-BlM8bhGmHydKbW9uAa0sYkU2Q-GvW1P5_YUJzbnOOUBStVy6oHjUMAcrNR8udOo11OV8e90w9lEU1gWSo2UKvk5nmjbBPXHXl/s1600/Screen+Shot+2018-09-21+at+11.22.29.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhY9QwSxoVzO62M9PyWM_Z2gdnO5j4w1Rtn8M-BlM8bhGmHydKbW9uAa0sYkU2Q-GvW1P5_YUJzbnOOUBStVy6oHjUMAcrNR8udOo11OV8e90w9lEU1gWSo2UKvk5nmjbBPXHXl/s1600/Screen+Shot+2018-09-21+at+11.22.29.png" /></a><br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiV7KfLzsg-a9i5PX1yx7ozmpuOOA-tIJGZ2Lc_hjwYBQT9YNF-NonhvcD7HJJb_qHsdEeMJVI8o3MJOZ9H-DV5DL9TAC_STv0J0A-VXyPjdlA4fogZ3JHqf_-O6uZ79_ViUw01/s1600/Screen+Shot+2018-09-21+at+11.22.47.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiV7KfLzsg-a9i5PX1yx7ozmpuOOA-tIJGZ2Lc_hjwYBQT9YNF-NonhvcD7HJJb_qHsdEeMJVI8o3MJOZ9H-DV5DL9TAC_STv0J0A-VXyPjdlA4fogZ3JHqf_-O6uZ79_ViUw01/s1600/Screen+Shot+2018-09-21+at+11.22.47.png" /></a><br />
<br />
We write in fact 2 tests to cover both scenarios: when editing or when adding a new article.<br />
<h3>
<br />Step 4: now we move back and extend the api</h3>
<br />
We design it to read the data from json and transfer it to the database object. For that we'll use a function transfer_from_json<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQ3tHURQLvWH7eT8eiuD6W9PEgdsZDr2_a_FRzar1pqc3TubfeRhAtbvTbcqIg189TDNTC6aaThWp_RFcQ7btcb_zY3crER7mGsLdy3v1oSIpbsw4_N-Vhyphenhyphen-FQp15dOCv-b9jS/s1600/step+5.1+back+to+api.png" imageanchor="1"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQ3tHURQLvWH7eT8eiuD6W9PEgdsZDr2_a_FRzar1pqc3TubfeRhAtbvTbcqIg189TDNTC6aaThWp_RFcQ7btcb_zY3crER7mGsLdy3v1oSIpbsw4_N-Vhyphenhyphen-FQp15dOCv-b9jS/s640/step+5.1+back+to+api.png" width="640" /></a><br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuihFXXWvNov3S6XT805gVdkIpnBKrKydHIdpjTNW1qWjmIOaawDWSF3fURaMgYz3rBj9HJriojhmBKx9zmnZsLKz6e-1k7lici9nWkz0PfGzi0YirGqx1Fa3K135Hj3xm5ChE/s1600/Screen+Shot+2018-09-21+at+13.15.24.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuihFXXWvNov3S6XT805gVdkIpnBKrKydHIdpjTNW1qWjmIOaawDWSF3fURaMgYz3rBj9HJriojhmBKx9zmnZsLKz6e-1k7lici9nWkz0PfGzi0YirGqx1Fa3K135Hj3xm5ChE/s1600/Screen+Shot+2018-09-21+at+13.15.24.png" /></a><br />
<br />
Once it passes, we move further:<br />
<br />
<h3>
Step 5: writing the transfer service</h3>
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUBYgU26ZWECsLYIQK9ED2CKcVjU_azxuWhQnbm68atBje-ibLgG_MscjJ2PPUlFVaVQJlM7sy8sYIMdC2rnQeqfRg1d4HTdKTJm9L2P03_Ce2bDL7-czeoWGVsuPDt0tJnVPV/s1600/step+6+trans+tests+red.png" imageanchor="1"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUBYgU26ZWECsLYIQK9ED2CKcVjU_azxuWhQnbm68atBje-ibLgG_MscjJ2PPUlFVaVQJlM7sy8sYIMdC2rnQeqfRg1d4HTdKTJm9L2P03_Ce2bDL7-czeoWGVsuPDt0tJnVPV/s640/step+6+trans+tests+red.png" width="640" /></a><br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYTL1Q1KZQg9mjP7VgksESjf-R-DqnL8jZ7sU4jE5J5xbMpDKCppyzZ4dE3u1QHcd7U0-fKW8WryZNt2JZ7VWJqa0hUulMi3T5iV5XAEGLLtwBEt1Dc_0-3NVcTdjimvUbaleb/s1600/Screen+Shot+2018-09-21+at+13.18.33.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYTL1Q1KZQg9mjP7VgksESjf-R-DqnL8jZ7sU4jE5J5xbMpDKCppyzZ4dE3u1QHcd7U0-fKW8WryZNt2JZ7VWJqa0hUulMi3T5iV5XAEGLLtwBEt1Dc_0-3NVcTdjimvUbaleb/s1600/Screen+Shot+2018-09-21+at+13.18.33.png" /></a><br />
<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYjvvQ_HpazOA-DmSZxQhY4mBSDOlyY8inpXTerQvQ1boK4L5kay9sCeFrWLi1c3CF1rmEg4-1mJh82WjzIT9tvq5dN4coE_A-iU7pGMiNr51PTzKO6iAweEKyHTssKgeMzeAx/s1600/Screen+Shot+2018-09-21+at+13.23.36.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYjvvQ_HpazOA-DmSZxQhY4mBSDOlyY8inpXTerQvQ1boK4L5kay9sCeFrWLi1c3CF1rmEg4-1mJh82WjzIT9tvq5dN4coE_A-iU7pGMiNr51PTzKO6iAweEKyHTssKgeMzeAx/s400/Screen+Shot+2018-09-21+at+13.23.36.png" /></a><br />
<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEzLRlPJwZ41NkFeHAMsULrrd8_hQlErA-AbKgfBnsCI44QsJRzupGbUE6MCGqFQX382fkyTRaTX83bIgwYYu1aavOmKvq_l890qVoHoTGsO5R3iVnW5fTlcDqroGbYsTYlKD1/s1600/Screen+Shot+2018-09-21+at+13.19.08.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEzLRlPJwZ41NkFeHAMsULrrd8_hQlErA-AbKgfBnsCI44QsJRzupGbUE6MCGqFQX382fkyTRaTX83bIgwYYu1aavOmKvq_l890qVoHoTGsO5R3iVnW5fTlcDqroGbYsTYlKD1/s1600/Screen+Shot+2018-09-21+at+13.19.08.png" /></a><br />
<br />
<br />
Now we make the test pass:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHgirw66EHsUGVUxOvCDeRn9AC93eHOjD7O0oV9hSMT0b8TmvZovMa6TjQDb8WvxTwbnempBJpjfRPq5m9XJ9nldviKLMQ6GoPnKfjCQLleKRhkaYt8jvCkTxfaXhzFRIp092A/s1600/step+7+tr+test+green.png" imageanchor="1"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHgirw66EHsUGVUxOvCDeRn9AC93eHOjD7O0oV9hSMT0b8TmvZovMa6TjQDb8WvxTwbnempBJpjfRPq5m9XJ9nldviKLMQ6GoPnKfjCQLleKRhkaYt8jvCkTxfaXhzFRIp092A/s640/step+7+tr+test+green.png" width="640" /></a><br />
<br />
<br />
<br />
Step 6: back to extending the api<br />
<br />
We now need to save the object with the data from the json into the db<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhv8AAKovVUqWbRlgel95T82HMaC_7PuCkOI68HTjAmV0eFF1eO6L8f5IP0GkVLTtbj_3ia83364Dud-arwDcl0N6ub3VengsNFUi_Z2bdH3zjgj5ofmbBasHvlp_gUnSukdTez/s1600/step+8+extend+apitest.png" imageanchor="1"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhv8AAKovVUqWbRlgel95T82HMaC_7PuCkOI68HTjAmV0eFF1eO6L8f5IP0GkVLTtbj_3ia83364Dud-arwDcl0N6ub3VengsNFUi_Z2bdH3zjgj5ofmbBasHvlp_gUnSukdTez/s640/step+8+extend+apitest.png" width="640" /></a><br />
<br />
For this, we extend the test:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPVELioea0dXS-9jM0L43cO6gIYEgiMQ4IHpjFK95Dun12SGivO1hWfxn4hbKxDJaGHxkLfWNy0g-Q6z4-iGJwNArIPu_Q6X3trfXZFuCiUQfxdxjinsLQiGKgRJEAC21BaAi7/s1600/Screen+Shot+2018-09-21+at+13.27.15.png" imageanchor="1"><img border="0" height="99" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPVELioea0dXS-9jM0L43cO6gIYEgiMQ4IHpjFK95Dun12SGivO1hWfxn4hbKxDJaGHxkLfWNy0g-Q6z4-iGJwNArIPu_Q6X3trfXZFuCiUQfxdxjinsLQiGKgRJEAC21BaAi7/s320/Screen+Shot+2018-09-21+at+13.27.15.png" width="320" /></a><br />
<br />
and make it pass, then we move back to implementing using TDD the save_database_object<br />
<br />
Step 7 extending the database_service to also save<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9smcLWk0GOa9bBcNfXW-H7EyUuOvbgX0nSB9hcQAd9xSNEYiLbMdSZ29FYHNf9AFr-acyByXQ8KRAjoKG9i77UYo5_eyTTUw6I-d2MNlCMyNf7TClSHDKi7WW25ibyp6VeILw/s1600/step+10+db+test+extension+red.png" imageanchor="1"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9smcLWk0GOa9bBcNfXW-H7EyUuOvbgX0nSB9hcQAd9xSNEYiLbMdSZ29FYHNf9AFr-acyByXQ8KRAjoKG9i77UYo5_eyTTUw6I-d2MNlCMyNf7TClSHDKi7WW25ibyp6VeILw/s640/step+10+db+test+extension+red.png" width="640" /></a><br />
<br />
Once we write the test, the code and make it pass:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDL9sKtucYm6u3kma7LxAyBkoBdDw8dSK80ArFbM1mF28S2H7wqjg8Jb8-E8gxP1WErM7zNgCegE7U6_hyN1QtEn0ENqNP2Y2Cg3tjFOjzgspUpNOSkruEtsorh5wFjIuCs6HO/s1600/step+11+db+ext+green.png" imageanchor="1"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDL9sKtucYm6u3kma7LxAyBkoBdDw8dSK80ArFbM1mF28S2H7wqjg8Jb8-E8gxP1WErM7zNgCegE7U6_hyN1QtEn0ENqNP2Y2Cg3tjFOjzgspUpNOSkruEtsorh5wFjIuCs6HO/s640/step+11+db+ext+green.png" width="640" /></a><br />
<br />
we move back to the api<br />
<br />
Step 7 extend the api<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJk2PCncwXJ4E2MasASc3230TVJKgasT0cnN3Alne4NtDKqa68IgmHIVeNy8lUTQDE_WAjdoCPjK0rNhub8QD11wu0_y_c9fcb-BSHxzLGaKImZ19Ufmns9LdG4tIaspMqvXRF/s1600/step+12.1.png" imageanchor="1"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJk2PCncwXJ4E2MasASc3230TVJKgasT0cnN3Alne4NtDKqa68IgmHIVeNy8lUTQDE_WAjdoCPjK0rNhub8QD11wu0_y_c9fcb-BSHxzLGaKImZ19Ufmns9LdG4tIaspMqvXRF/s640/step+12.1.png" width="640" /></a><br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_6tDnrD6PMZOBjgIwrSQCyLq6IkA5pWA8KQR3LHozrDUX4urkT60AYaNf5FaspUxE41DYkYV9K5Fl4Al3S0jsJfMh4okYpFAGUlHNSzdub0wpB2f1ZhQyTKXP6HaMjLja8g28/s1600/Screen+Shot+2018-09-21+at+13.31.33.png" imageanchor="1"><img border="0" height="105" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_6tDnrD6PMZOBjgIwrSQCyLq6IkA5pWA8KQR3LHozrDUX4urkT60AYaNf5FaspUxE41DYkYV9K5Fl4Al3S0jsJfMh4okYpFAGUlHNSzdub0wpB2f1ZhQyTKXP6HaMjLja8g28/s320/Screen+Shot+2018-09-21+at+13.31.33.png" width="320" /></a><br />
<br />
Step 8: TDD the extension:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimHs2uCfbbeydtETDMKbbVdWuG6JrRWMBeDPAvLFzJi20mIyKO30Wx555QzuPUTzD14ZkpRNbOD8_192xTFFCzL0-aJPXRZEYy3EWxhDp0Ma3iYtPKluayqUpfQPbcVMJ25DRN/s1600/step+13+ext+tr+red.png" imageanchor="1"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimHs2uCfbbeydtETDMKbbVdWuG6JrRWMBeDPAvLFzJi20mIyKO30Wx555QzuPUTzD14ZkpRNbOD8_192xTFFCzL0-aJPXRZEYy3EWxhDp0Ma3iYtPKluayqUpfQPbcVMJ25DRN/s640/step+13+ext+tr+red.png" width="640" /></a><br />
<br />
Until we make it pass:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrww-VaPqC7NYU6hc4ngY-2lK2gfiH8Ug2Yf0t5C_MHJAkVjN4YpP9cul_B_Zi72cwxxJsb3575Tzkqr6V5tHjQ6LNmO4hoBKgIkP6knBusrckkGZesCBPlI2vOE-WmtM3FUxc/s1600/step+15.png" imageanchor="1"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrww-VaPqC7NYU6hc4ngY-2lK2gfiH8Ug2Yf0t5C_MHJAkVjN4YpP9cul_B_Zi72cwxxJsb3575Tzkqr6V5tHjQ6LNmO4hoBKgIkP6knBusrckkGZesCBPlI2vOE-WmtM3FUxc/s640/step+15.png" width="640" /></a><br />
<br />
<br />
After a few more intermediary steps:<br />
<br />
...<br />
<br />
We will TDD all the necessary code to make the acceptance test pass:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRw3UZg0soQa5UkkGD8QetPG_IixWBz3Hui0SJaIPBKM1HzX9xjVo5xKeqYI28zkpuObmlowtAanu3P5ApGb2NL2uwfG_glu8JEUMKu4F9ITJbrpgF7riiO_xxNhCpIbnnUiuN/s1600/step+final.png" imageanchor="1"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRw3UZg0soQa5UkkGD8QetPG_IixWBz3Hui0SJaIPBKM1HzX9xjVo5xKeqYI28zkpuObmlowtAanu3P5ApGb2NL2uwfG_glu8JEUMKu4F9ITJbrpgF7riiO_xxNhCpIbnnUiuN/s640/step+final.png" width="640" /></a><br />
<br />
<br /><div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-44238412861899015882018-08-29T16:50:00.000+03:002018-08-29T16:59:08.262+03:00Outside-In TDD the bank kata in Clojure/ClojureScript<br />
Code: <a href="https://github.com/DanBunea/katas/tree/master/outside-in%20tdd%20bank%20kata" target="_blank">https://github.com/DanBunea/katas/tree/master/outside-in%20tdd%20bank%20kata </a><br />
<br />
<br />
In "London school" TDD or Outside-In we start from an acceptance test, then write unit tests. This is better explained as the double loop of TDD:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNhQkmWFr8q5CX0s_KMn3AXOzCc6x7K2w-md-zy57iJn7396nChyphenhyphenMRJ1Qxvpon-T-8JFK7lGC1JoWDmj-Wc8LfVUAi0kLayKK2mhNiksvy-7emVc3-tHiPO4NI4konBIAsMcv2/s1600/tdd-double-loop.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNhQkmWFr8q5CX0s_KMn3AXOzCc6x7K2w-md-zy57iJn7396nChyphenhyphenMRJ1Qxvpon-T-8JFK7lGC1JoWDmj-Wc8LfVUAi0kLayKK2mhNiksvy-7emVc3-tHiPO4NI4konBIAsMcv2/s1600/tdd-double-loop.png" /></a><br />
<br />
<br />
Some time ago, I found this <u>great</u> step by step explanation of Outside-In TDD, by Sandro Mancuso from <a href="https://codurance.com/videos/2015-05-12-outside-in-tdd-part-1/" target="_blank">Codurance</a>:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/XHnuMjah6ps/0.jpg" frameborder="0" height="266" src="https://www.youtube.com/embed/XHnuMjah6ps?feature=player_embedded" width="320"></iframe></div>
<br />
<br />
However it was all Java and obviously OOP, so I wanted do the same thing but in Clojure, and fully functional: functions and not classes. So here we go:<br />
<br />
<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfQZRSzeWe3_gbOnbWNqYqU1Zhb4rbIdRdTPmDcunezFpDH9HxoDZgHDAQipslcWQ63wt47Azh_aPn4eh-Hf8S30qWC0iOA9JKYLi_oi63G2z8Zhg2Jc2e57F4Q9og-_LTXjVC/s1600/Screen+Shot+2018-08-28+at+16.36.15.png" imageanchor="1"><img border="0" height="457" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfQZRSzeWe3_gbOnbWNqYqU1Zhb4rbIdRdTPmDcunezFpDH9HxoDZgHDAQipslcWQ63wt47Azh_aPn4eh-Hf8S30qWC0iOA9JKYLi_oi63G2z8Zhg2Jc2e57F4Q9og-_LTXjVC/s640/Screen+Shot+2018-08-28+at+16.36.15.png" width="640" /></a><br />
<br />
Restrictions (adapted for functional):<br />
<br />
1. Start with a module account that has the following functions:<br />
<br />
<span style="color: blue;">(defn deposit [account amount] ...)</span><br />
<div>
<span style="color: blue;">(defn withdraw [account amount] ...)</span></div>
<div>
<span style="color: blue;">(defn print-statement [account] ...)</span><br />
<br />
Threat the methods, as if they cannot return any values!<br />
<br />
2. You're not allowed to add any new functions to the module<br />
3. Strings and integeres are to be used for dates and amounts for simplicity<br />
4. Don't worry about spacing in the statement printed at the console</div>
<div>
<br /></div>
<br />
<h2>
Step 1: The acceptance test (the big loop)</h2>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLAt-rY6Ink-iLK6enBEIm3H7vcyDV6RsRHrUuv5-aQMJI9DkrnPt7vKxpVOj1G_P21OQtDuUjQvwjGZ_xaMrUen_gEbnNK_jjOcyr6kquFB2wObgjxar-XdKAZzb7cKTntYWd/s1600/Screen+Shot+2018-08-27+at+12.43.08.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLAt-rY6Ink-iLK6enBEIm3H7vcyDV6RsRHrUuv5-aQMJI9DkrnPt7vKxpVOj1G_P21OQtDuUjQvwjGZ_xaMrUen_gEbnNK_jjOcyr6kquFB2wObgjxar-XdKAZzb7cKTntYWd/s1600/Screen+Shot+2018-08-27+at+12.43.08.png" /></a><br />
<br />
<br />
we need to take this test, unit it fails for the right reason: the results of the console-print are not what was expected, so we write two modules:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtKEhjPzjHV-7DxOZYSy4fpFOX731aJ_7LIgLB-cZUonB_JrnUd5cjrorGx26iCXYynsabfvahRwOOM-pkO0bFx8yDu5RY3Jqe_CnzQ5dEEiZ8HTEDM5lppS9exLQyNrbw_sbV/s1600/console+1.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtKEhjPzjHV-7DxOZYSy4fpFOX731aJ_7LIgLB-cZUonB_JrnUd5cjrorGx26iCXYynsabfvahRwOOM-pkO0bFx8yDu5RY3Jqe_CnzQ5dEEiZ8HTEDM5lppS9exLQyNrbw_sbV/s1600/console+1.png" /></a><br />
<br />
and account.cljs:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjI1Glbu6aOgrgEvuhZWNbG83yw4jyMufWwy4t_2fQ0uuUagxgSkqoWoYs6ZUwldbJAYZTyEqHMIP1WnoYtWfNpQ9n_oNAlgSWv_4ozOC3jM2lHEHp0OhhI3WCnN5-Y6tuYbfn5/s1600/account+1.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjI1Glbu6aOgrgEvuhZWNbG83yw4jyMufWwy4t_2fQ0uuUagxgSkqoWoYs6ZUwldbJAYZTyEqHMIP1WnoYtWfNpQ9n_oNAlgSWv_4ozOC3jM2lHEHp0OhhI3WCnN5-Y6tuYbfn5/s1600/account+1.png" /></a><br />
<br />
and now the test:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLfO-OBMZYnycNN4Z_9IQTpIMjg1OMS9viL35atq6Vn8lfSH2vMsJdT8RrZK5uSK8-DHGB4xnOQ_AFLAnXoLsGkTLtywVIxwLfQ91ctynjFTDz2diqtcBPDprbf3YOghBoHFXx/s1600/Screen+Shot+2018-08-27+at+12.43.21.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLfO-OBMZYnycNN4Z_9IQTpIMjg1OMS9viL35atq6Vn8lfSH2vMsJdT8RrZK5uSK8-DHGB4xnOQ_AFLAnXoLsGkTLtywVIxwLfQ91ctynjFTDz2diqtcBPDprbf3YOghBoHFXx/s1600/Screen+Shot+2018-08-27+at+12.43.21.png" /></a><br />
<br />
<h2>
Step 2: The first unit test - account module - the business layer (the small loop)</h2>
<br />
The very first module we should test is account, so we make account_tests. For account, we could test the deposit function. Considering we might have a module which will actually do the depositing/withdrawals in a database or something, that module will be separate and we'll call it transaction-repository. So we could test the interactions between modules like:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-vbKjKbUdEsXluSzDnXKGwYmaZnnC82QrtGTlb6JDk8qHn7Svih6hbGKXtAxgh9XqHPnrxFQFtrNHMgCqvnYy9Yoo6z7Lqi2ziUOzR_Y7szRy6fmgmOnjbbIrmwmHP165Tvbl/s1600/Screen+Shot+2018-08-27+at+16.44.18.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-vbKjKbUdEsXluSzDnXKGwYmaZnnC82QrtGTlb6JDk8qHn7Svih6hbGKXtAxgh9XqHPnrxFQFtrNHMgCqvnYy9Yoo6z7Lqi2ziUOzR_Y7szRy6fmgmOnjbbIrmwmHP165Tvbl/s1600/Screen+Shot+2018-08-27+at+16.44.18.png" /></a><br />
<br />
We expect that when we invoke deposit from the account module, the add-deposit from the transaction-repository will be invoked.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_F01-vWLvbQLUJaH2c40r3uQfJofIk5pAupkAYLmPH4p68iahFsAbrIXKb4TK5duEbAMSzPDtOiUmJqiUb3ATwEJxW1uaa71LAsFmuOmP3grqrJUEEG3-XpLSuhjILXpX7YeM/s1600/Screen+Shot+2018-08-27+at+17.12.05.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_F01-vWLvbQLUJaH2c40r3uQfJofIk5pAupkAYLmPH4p68iahFsAbrIXKb4TK5duEbAMSzPDtOiUmJqiUb3ATwEJxW1uaa71LAsFmuOmP3grqrJUEEG3-XpLSuhjILXpX7YeM/s1600/Screen+Shot+2018-08-27+at+17.12.05.png" /></a><br />
<br />
we make it pass,<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLNwWKdPmC-MAha17kvWutaxoJm2s7ffMi_cPzMLGtLq53crPO-2G1trP47F4q7ecHj4Psq9_2vpxhNFku6nJXhPpSX2qrR3fbQWpYqalxo3Blg4QXviN_yDNhoJThQMv2ntD1/s1600/Screen+Shot+2018-08-27+at+17.13.50.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLNwWKdPmC-MAha17kvWutaxoJm2s7ffMi_cPzMLGtLq53crPO-2G1trP47F4q7ecHj4Psq9_2vpxhNFku6nJXhPpSX2qrR3fbQWpYqalxo3Blg4QXviN_yDNhoJThQMv2ntD1/s1600/Screen+Shot+2018-08-27+at+17.13.50.png" /></a><br />
<br />
and continue with withdraw and later with print-statement for which we'll do a separate module: statement-printer. The final test will be:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgl2OZeS4hwj8y49f2L3oWFB0y4kcIs3c-7xvnZTJ431SEouLmRBhBwX7IVV4-Rf9uXBsZwCKA6w2XFRpXMq5v6wTPAPvODFxPaa_GgjJBtt7YQ9Yq2WhXDrWITBlMZxsweC1hg/s1600/Screen+Shot+2018-08-27+at+17.17.13.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgl2OZeS4hwj8y49f2L3oWFB0y4kcIs3c-7xvnZTJ431SEouLmRBhBwX7IVV4-Rf9uXBsZwCKA6w2XFRpXMq5v6wTPAPvODFxPaa_GgjJBtt7YQ9Yq2WhXDrWITBlMZxsweC1hg/s1600/Screen+Shot+2018-08-27+at+17.17.13.png" /></a><br />
<br />
and:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1IwRhyphenhyphen8EFYv66-LiqLVuD6IhOvHYl32nKwi4osvNdSJY8mYHA56KAhPj1tH2ZRmPVK-g0ovRvUpHZ4LldZ3AusnRJ4Uf1jXbATCKbxV4GU4VPb33o1NvoYAG72bUnsXGW_mom/s1600/Screen+Shot+2018-08-28+at+16.24.29.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1IwRhyphenhyphen8EFYv66-LiqLVuD6IhOvHYl32nKwi4osvNdSJY8mYHA56KAhPj1tH2ZRmPVK-g0ovRvUpHZ4LldZ3AusnRJ4Uf1jXbATCKbxV4GU4VPb33o1NvoYAG72bUnsXGW_mom/s1600/Screen+Shot+2018-08-28+at+16.24.29.png" /></a><br />
<br />
and:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9Hojuz3GKsYP-Ab1ClQpehcz2eRU6RRvXi4xKa0Po-Kjl43a6hOQnGruJX9p9wwNueJgpbB7aPZ4joQk2u-hxLpMpj_icD0qcLtBHJULmBikdpC_7NLrGgY20azHJEi9n_5-o/s1600/Screen+Shot+2018-08-27+at+17.17.45.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9Hojuz3GKsYP-Ab1ClQpehcz2eRU6RRvXi4xKa0Po-Kjl43a6hOQnGruJX9p9wwNueJgpbB7aPZ4joQk2u-hxLpMpj_icD0qcLtBHJULmBikdpC_7NLrGgY20azHJEi9n_5-o/s1600/Screen+Shot+2018-08-27+at+17.17.45.png" /></a><br />
<br />
<br />
<h2>
Step 3: the unit tests for the "data layer" (the small loop)</h2>
Like for the account, we start with a simple test, where we make a deposit and check if all-transactions will return it. We also need to make sure it's on a certain date. We make the test pass, and we move on to withdraw, ending up with:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1H8hrWWn-yJUsXBJ7pzpCWMzmyPVJFsGRAmTv511Aed0FIW_Y6tcPrTLjAaHKIZVOt1cjsXq-3n9b2XhEh1DejRkO0i0xDZvskJAh7wLvR7JS3_YR0h9gQLL4bBkNGmz5VNQ8/s1600/Screen+Shot+2018-08-27+at+17.25.18.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1H8hrWWn-yJUsXBJ7pzpCWMzmyPVJFsGRAmTv511Aed0FIW_Y6tcPrTLjAaHKIZVOt1cjsXq-3n9b2XhEh1DejRkO0i0xDZvskJAh7wLvR7JS3_YR0h9gQLL4bBkNGmz5VNQ8/s1600/Screen+Shot+2018-08-27+at+17.25.18.png" /></a><br />
<br />
and:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHqSY9zHmTFWCLfolRsfRv7DsMFSy70DfAv933GWYoFsZCgqLv6QhhRNiFFh_7-XJoLVSmeih_pG9UF9YYqmpcCF53oAKrlfoiGWoM49-ttm3S8Uihl-mzObZ8LjPHEDYWLfZv/s1600/rep.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHqSY9zHmTFWCLfolRsfRv7DsMFSy70DfAv933GWYoFsZCgqLv6QhhRNiFFh_7-XJoLVSmeih_pG9UF9YYqmpcCF53oAKrlfoiGWoM49-ttm3S8Uihl-mzObZ8LjPHEDYWLfZv/s1600/rep.png" /></a><br />
<br />
and:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKH4pft4DfRq3iZFOxlD_MTpAg_yNyI_SKavYXlgoUunnT-7lPsFub6j1WgWIAZh6dFFHEp57NJYpZeJoRhtHB1zRvUZgHagDOhWfCwOxTyZYH1ULcObf2DSsnNeR-uodXRfFB/s1600/Screen+Shot+2018-08-27+at+17.25.00.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKH4pft4DfRq3iZFOxlD_MTpAg_yNyI_SKavYXlgoUunnT-7lPsFub6j1WgWIAZh6dFFHEp57NJYpZeJoRhtHB1zRvUZgHagDOhWfCwOxTyZYH1ULcObf2DSsnNeR-uodXRfFB/s1600/Screen+Shot+2018-08-27+at+17.25.00.png" /></a><br />
<br />
<br />
<h2>
Step 4: Unit testing the statement-printer (the small loop)</h2>
<br />
Now, we'll go straight to the results, even though I used TDD to get to it:<br />
<br />
The tests:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNEaWa8fN_KpYxquGVMj65kykFIHeSyYMJcmr6QLkUZ6vXCu6R3AcpUyunWIBVxAV7dPD884hc8nmhFudaqP9fWM-1hz0-juhZDTsX9xKnF3nwqckOsfqmrLB0j5Ef-5EuzE_r/s1600/Screen+Shot+2018-08-28+at+15.28.03.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNEaWa8fN_KpYxquGVMj65kykFIHeSyYMJcmr6QLkUZ6vXCu6R3AcpUyunWIBVxAV7dPD884hc8nmhFudaqP9fWM-1hz0-juhZDTsX9xKnF3nwqckOsfqmrLB0j5Ef-5EuzE_r/s1600/Screen+Shot+2018-08-28+at+15.28.03.png" /></a><br />
<br />
<br />
The code obtained:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjv2fgOZ3ji8DmL6HKLR2JY9eRB9Ch9nGnR7C0xRnTN2zOa6BFcEpBJ6K_9ijtbk34TSqcn-vkndMvdxmaEKlnSPICGJiedNb7-nJuyjorv2GD8mXJyWEh7ZGYlDmF6_IKbGVS2/s1600/Screen+Shot+2018-08-28+at+16.14.47.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjv2fgOZ3ji8DmL6HKLR2JY9eRB9Ch9nGnR7C0xRnTN2zOa6BFcEpBJ6K_9ijtbk34TSqcn-vkndMvdxmaEKlnSPICGJiedNb7-nJuyjorv2GD8mXJyWEh7ZGYlDmF6_IKbGVS2/s1600/Screen+Shot+2018-08-28+at+16.14.47.png" /></a><br />
<br />
The tests result:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbd1MNED1o8V_eP9eSKfv8cNru2RSTJU0ynHMpPxqZqK3JGWP6NUlpRsJ8Rv0-hhgnYCb2BqMon7acaEzt1HRKg051Mh8mUrKMqR0ArETAhMdPAmBwMVB3HjhrcUZ0lhKSEo54/s1600/Screen+Shot+2018-08-28+at+15.28.51.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbd1MNED1o8V_eP9eSKfv8cNru2RSTJU0ynHMpPxqZqK3JGWP6NUlpRsJ8Rv0-hhgnYCb2BqMon7acaEzt1HRKg051Mh8mUrKMqR0ArETAhMdPAmBwMVB3HjhrcUZ0lhKSEo54/s1600/Screen+Shot+2018-08-28+at+15.28.51.png" /></a><br />
<br />
Let's not forget that our console-print function is still mocked, and the implementation is:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhojSsqvWaWclG1XUPLdrRX9HGgl5na6u0YxoDIiTO1R-TJcC89e9TL9nvtjS0qoJH9RBB6VcNwANsOwkzA3EOwEbThCqgH7CpZKp-Vld_vuogTewezaHnwz1S0KbPdGyzzHGfQ/s1600/Screen+Shot+2018-08-28+at+15.31.30.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhojSsqvWaWclG1XUPLdrRX9HGgl5na6u0YxoDIiTO1R-TJcC89e9TL9nvtjS0qoJH9RBB6VcNwANsOwkzA3EOwEbThCqgH7CpZKp-Vld_vuogTewezaHnwz1S0KbPdGyzzHGfQ/s1600/Screen+Shot+2018-08-28+at+15.31.30.png" /></a><br />
<br />
and we can now implement it:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8W85ZfNo8mnSv_u8UpXuTeF3SWc-iaGgJn25KNydjbBmXfkpyM5O0z-jBvgCOKODPDGcuU4EURXYISrECk8iLYSSHhvIUvEGssdv9zevKCg0b8XDEM6IiQne19rjpDYePnHIr/s1600/Screen+Shot+2018-08-28+at+16.20.00.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8W85ZfNo8mnSv_u8UpXuTeF3SWc-iaGgJn25KNydjbBmXfkpyM5O0z-jBvgCOKODPDGcuU4EURXYISrECk8iLYSSHhvIUvEGssdv9zevKCg0b8XDEM6IiQne19rjpDYePnHIr/s1600/Screen+Shot+2018-08-28+at+16.20.00.png" /></a><br />
<br />
Tests still pass.<br />
<br />
<h2>
Step 5: Going back to the acceptance test (the big loop)</h2>
<br />
We will modify the test, mocking the dates delivered, as well as console-print:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQfQEVzyOIM2fiKJu-WGQjegeElCPxjR21s5wqE2XlsQoO3fuWz1ABBWfiGKCii96VfC6R6-pYFs_QBm4O7Rnu6tQA5UvTQ4xu4EBgOwrnqJFDWwnhw87LYPFoiOXn-RtXa6aW/s1600/Screen+Shot+2018-08-28+at+16.20.18.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQfQEVzyOIM2fiKJu-WGQjegeElCPxjR21s5wqE2XlsQoO3fuWz1ABBWfiGKCii96VfC6R6-pYFs_QBm4O7Rnu6tQA5UvTQ4xu4EBgOwrnqJFDWwnhw87LYPFoiOXn-RtXa6aW/s1600/Screen+Shot+2018-08-28+at+16.20.18.png" /></a><br />
<br />
<br />
And when we run all the tests:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFzKvlnyyORyANERTTYn_tKVG3q2WE5Y6oTMN9G3XOdql_rtlVMnJx3Atpy5eessdTDGN9FiMa0VejhTMDNs2lzqJfCtiALw56yPuLwzRUY9-nEFNygXDSSAnRM_rnGqWgkzww/s1600/Screen+Shot+2018-08-28+at+16.21.01.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFzKvlnyyORyANERTTYn_tKVG3q2WE5Y6oTMN9G3XOdql_rtlVMnJx3Atpy5eessdTDGN9FiMa0VejhTMDNs2lzqJfCtiALw56yPuLwzRUY9-nEFNygXDSSAnRM_rnGqWgkzww/s1600/Screen+Shot+2018-08-28+at+16.21.01.png" /></a><br />
<br />
<br />
<h2>
Conclusion</h2>
<br />
Starting from the outside, we could test-drive the design and the code.<br />
<br />
<i>acceptance-test -> </i><br />
<i> account-tests->account-></i><br />
<i> transaction-respository-tests->transaction-repository-></i><br />
<div>
<i> statement-printer-tests->statement-printer->console</i></div>
<div>
<br /></div>
<br /><div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-63671766649605280702018-07-02T15:47:00.000+03:002018-07-02T15:47:29.799+03:00Simple , robust code: part one, simplicity<h2>
1. Simple as the oposite of complex</h2>
<br />
Complexity in software is the root of all evil, and simplicity is the oposite of complexity. Simple is not the same as easy, because sometimes we make software complex just because it is easy (think of adding a library from which you need just a function, which then needs to be upgraded and it's incompatible with other libraries etc).<br />
<br />
A complex sistem is like this, where is is very hard to figure out what is going on, thus it cannot be debugged, extended or changed:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7j5fa9Jns3jZ-kmPJ9v3EiIQ9xjokofc_Lya-I1IED89Pn2UIKPwHY3rkfSCOYP1XWuJAmEtCn85efblTBTUoktCxzUjrPyCfUcF3f-slE1ATff4d1J5PbjkAa4Lmm00P2fdd/s1600/Screen+Shot+2018-07-02+at+10.50.05.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="543" data-original-width="837" height="207" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7j5fa9Jns3jZ-kmPJ9v3EiIQ9xjokofc_Lya-I1IED89Pn2UIKPwHY3rkfSCOYP1XWuJAmEtCn85efblTBTUoktCxzUjrPyCfUcF3f-slE1ATff4d1J5PbjkAa4Lmm00P2fdd/s320/Screen+Shot+2018-07-02+at+10.50.05.png" width="320" /></a></div>
<br />
<span id="goog_410369491"></span><span id="goog_410369492"></span><br />
<br />
and a simple one is the oposite:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmase4IOL9jTz679XYDtGDxD0rxzJ0AMR6-Wyq8gjqv_fE9u-wgL3ugIYVFImYZCwVqB0h9hGuP7Ij3f4BBsxFu4pRa6ecOIZ2pFg3e2_B5BMtuNw4Y6nyEmCd_8YIOfwNv7V-/s1600/Screen+Shot+2018-07-02+at+10.50.15.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="255" data-original-width="932" height="87" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmase4IOL9jTz679XYDtGDxD0rxzJ0AMR6-Wyq8gjqv_fE9u-wgL3ugIYVFImYZCwVqB0h9hGuP7Ij3f4BBsxFu4pRa6ecOIZ2pFg3e2_B5BMtuNw4Y6nyEmCd_8YIOfwNv7V-/s320/Screen+Shot+2018-07-02+at+10.50.15.png" width="320" /></a></div>
<br />
<br />
<br />
<h2>
2. Simplicity in software DATA and FUNCTIONS*</h2>
We use computers to compute (apply functions) some data we need (the final state of a system), given some initial data (initial state of the system). So if we drastically reduce what software does, we end up with just <b>data</b> and <b>functions</b>.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioAcEG0_oeXOSWPDWDMxKiKqNulHgGzDXAifBxeFi9Dkgk1X39g5WWcYLdoGdET4HPwJikbIhr6iUB6mI2vzyJWpfE9G8AJQKD6ZzmWBycBPHIxYlkgJbpuMv28P9U6AZqJJB4/s1600/Screen+Shot+2018-07-02+at+11.00.37.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="408" data-original-width="287" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioAcEG0_oeXOSWPDWDMxKiKqNulHgGzDXAifBxeFi9Dkgk1X39g5WWcYLdoGdET4HPwJikbIhr6iUB6mI2vzyJWpfE9G8AJQKD6ZzmWBycBPHIxYlkgJbpuMv28P9U6AZqJJB4/s320/Screen+Shot+2018-07-02+at+11.00.37.png" width="225" /></a></div>
<br />
<br />
<br />
Example:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpgCCu53y-iwa5o1X6Dpk-2XL1RJ0IDpwtyzV6X7aaTW3-fkNU4LKLGr6lk8vOuWAJUiy2LsGBCURhu8tWBp5V9MjWk5hxE_zkHui_9AEIMBmYBGEeQnDYjnMYogr_Hz9FHf-A/s1600/Screen+Shot+2018-07-02+at+11.02.19.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="509" data-original-width="998" height="163" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpgCCu53y-iwa5o1X6Dpk-2XL1RJ0IDpwtyzV6X7aaTW3-fkNU4LKLGr6lk8vOuWAJUiy2LsGBCURhu8tWBp5V9MjWk5hxE_zkHui_9AEIMBmYBGEeQnDYjnMYogr_Hz9FHf-A/s320/Screen+Shot+2018-07-02+at+11.02.19.png" width="320" /></a></div>
<br />
<br />
<br />
Obviously this sounds overly simplistic, real code is more complex, more functions are needed.<br />
<br />
<code style="color: black; word-wrap: normal;">
function greed(name){<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>var a = ["hello ", name];<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>var b =<b>capitalize_first_letter</b>(a);<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>var c =<b>concat</b>(b);<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>return c;<br />
}<br />
</code>
<br />
<br />
or we could:<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6oNZZzxFfmZtq975z2bBvo-uLCxk5TahOJISBuT95Mp8bTSOfrFKFc5D75VjPZW9mCfbaGMoUGQDzB02hlNTeSma1jKCp8j3gTElIoL0p84vsDuIhogswVXjQ8edMDHk7D5mQ/s1600/Screen+Shot+2018-07-02+at+11.04.34.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="569" data-original-width="996" height="182" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6oNZZzxFfmZtq975z2bBvo-uLCxk5TahOJISBuT95Mp8bTSOfrFKFc5D75VjPZW9mCfbaGMoUGQDzB02hlNTeSma1jKCp8j3gTElIoL0p84vsDuIhogswVXjQ8edMDHk7D5mQ/s320/Screen+Shot+2018-07-02+at+11.04.34.png" width="320" /></a></div>
<br />
<br />
<br />
Which starts to look like a pipe, where you send the initial_state, and expect at the end the final state.<br />
<br />
Now if we need to solve a real world problem, I guess we could solve it by having:<br />
- lots of simple functions, that take as input one parameter and return one parameter<br />
- because the have one parameter in and one parameter our they can be composed<br />
- simple functions put together as a pipeline and can solve very complex problems in a very simple way<br />
<br />
<h2>
3. Functional composition</h2>
<br />
Now we could compose the two functions into just one:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkI4vpFSgIaT6KcoUOvjsF4K9nz5hmFIkQvDhQcshiiN9BDoVOa9xF6BvOM-OdIvbC-bKmJAlme6vtuR1cqwhOAQSviJuRcMx6PHywxI3Ioo2CSAXknQrmuKtJDs1fxGiRzGdc/s1600/Screen+Shot+2018-07-02+at+14.54.55.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="760" data-original-width="1019" height="297" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkI4vpFSgIaT6KcoUOvjsF4K9nz5hmFIkQvDhQcshiiN9BDoVOa9xF6BvOM-OdIvbC-bKmJAlme6vtuR1cqwhOAQSviJuRcMx6PHywxI3Ioo2CSAXknQrmuKtJDs1fxGiRzGdc/s400/Screen+Shot+2018-07-02+at+14.54.55.png" width="400" /></a></div>
<br />
<br />
<h2>
4. Example: From complex to simple using functional composition</h2>
<br />
A few years ago, I made a practical example. I'll add it simplified here.<br />
<span style="color: #333333; font-family: "helvetica neue" , "helvetica" , "arial" , "lucida grande" , sans-serif; font-size: 14px;"><br /></span>
<span style="color: #333333; font-family: "helvetica neue" , "helvetica" , "arial" , "lucida grande" , sans-serif; font-size: 14px;">Requirement: in the json that we receive on a server, we need to have a key “measurement”, that is mandatory, cannot be null, needs to be a string and cannot be empty string, </span><span style="color: #333333; font-family: "helvetica neue" , "helvetica" , "arial" , "lucida grande" , sans-serif; font-size: 14px;">Then we also need to make sure the length of the string is between 3 and 8 characters, and cannot be some reserved words like “password” or “archived". So the code is like:</span><br />
<span style="color: #333333; font-family: "helvetica neue" , "helvetica" , "arial" , "lucida grande" , sans-serif; font-size: 14px;"><br /></span>
<br />
<div style="-webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; caret-color: rgb(51, 51, 51); color: #333333; font-family: "helvetica neue", helvetica, arial, "lucida grande", sans-serif; font-size: 14px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-decoration: none; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px;">
<div style="margin: 0px;">
</div>
</div>
<div id="mb-reply" style="-webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; caret-color: rgb(51, 51, 51); color: #333333; font-family: "helvetica neue", helvetica, arial, "lucida grande", sans-serif; font-size: 14px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-decoration: none; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px;">
</div>
<br />
<div style="-webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; caret-color: rgb(51, 51, 51); color: #333333; font-family: "helvetica neue", helvetica, arial, "lucida grande", sans-serif; font-size: 14px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-decoration: none; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px;">
<pre style="background-color: #f0f0f0; background-position: initial initial; background-repeat: initial initial; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; margin: 0px; overflow: auto; padding: 0px; text-align: left; width: 646.46875px;"><code style="color: black; word-wrap: normal;"> def validate_simplest_json(json):
errors = []
if not json.has_key("measurement"):
errors.append("measurement cannot be missing")
else:
if json["measurement"]==None:
errors.append("measurement cannot be null")
else:
if not isinstance(json["measurement"], str) and not isinstance(json["measurement"], unicode):
errors.append("measurement needs to string or unicode")
else:
lenm=len(json["measurement"].strip())
if lenm==0:
errors.append("measurement cannot be an empty string")
else:
if lenm<3: data-blogger-escaped-div="">
errors.append("measurement needs at least 3 characters")
elif lenm&gt;10:
errors.append("measurement needs at most 10 characters")
elif json["measurement"].strip().lower() in ["archived","password"]:
errors.append("measurement has a value which is not allowed")
return errors
</code></pre>
</div>
<span style="color: #333333; font-family: "helvetica neue" , "helvetica" , "arial" , "lucida grande" , sans-serif; font-size: 14px;"><br /></span>
<span style="color: #333333; font-family: "helvetica neue" , "helvetica" , "arial" , "lucida grande" , sans-serif; font-size: 14px;"><br /></span>
<span style="color: #333333; font-family: "helvetica neue" , "helvetica" , "arial" , "lucida grande" , sans-serif; font-size: 14px;">Removing complexity can mean, more linear code, and an initial state, and simple composable functions:</span><br />
<br />
<div id="mb-reply" style="caret-color: rgb(51, 51, 51); color: #333333; font-family: "helvetica neue", helvetica, arial, "lucida grande", sans-serif; font-size: 14px; line-height: 20px;">
<div id="mb-reply">
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"> ValidationState = namedtuple("ValidationState","json key errors exit”)
</code></pre>
</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
then I will extract the actual validations in simple functions, like:</div>
<div id="mb-reply">
<div>
<br /></div>
<div>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"> def validate_simplest_json_imperative_linear_with_state(json):
initial_state = ValidationState(json=json, key="measurement",errors=[], exit=False)
state = validate_key_exists(initial_state)
if not state.exit:
state = validate_not_null(state)
if not state.exit:
state = validate_string_or_unicode(state)
if not state.exit:
state = validate_not_empty_string(state)
if not state.exit:
state = validate_length(state, 3,10)
if not state.exit:
state = validate_not_in(state, ["archived","password"])
return state.errors
</code></pre>
</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
And the functions are like:</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"> def validate_key_exists(state):
print validate_key_exists.__name__,state
if not key_exists(state.json,state.key):
return state._replace(errors = state.errors+["{0} cannot be missing".format(state.key)])._replace(exit=True)
return state
def validate_not_null(state):
print validate_not_null.__name__,state
if value_null(state.json,state.key):
return state._replace(errors = state.errors+["{0} cannot be null".format(state.key)])._replace(exit=True)
return state
</code></pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;">...</code></pre>
</div>
</div>
</div>
<br />
<br />
<div id="mb-reply" style="caret-color: rgb(51, 51, 51); color: #333333; font-family: "helvetica neue", helvetica, arial, "lucida grande", sans-serif; font-size: 14px;">
The code looks is now a series of functions that run with the result of the previous function if the exit parameter is not set to True. So basically having 2 functions f,g they’ll be composed like:</div>
<div id="mb-reply" style="caret-color: rgb(51, 51, 51); color: #333333; font-family: "helvetica neue", helvetica, arial, "lucida grande", sans-serif; font-size: 14px;">
<br /></div>
<div id="mb-reply" style="caret-color: rgb(51, 51, 51); color: #333333; font-family: "helvetica neue", helvetica, arial, "lucida grande", sans-serif; font-size: 14px;">
<i><b>initial_state = …</b></i></div>
<div id="mb-reply" style="caret-color: rgb(51, 51, 51); color: #333333; font-family: "helvetica neue", helvetica, arial, "lucida grande", sans-serif; font-size: 14px;">
<i><b>state = f(initial_state)</b></i></div>
<div id="mb-reply" style="caret-color: rgb(51, 51, 51); color: #333333; font-family: "helvetica neue", helvetica, arial, "lucida grande", sans-serif; font-size: 14px;">
<i><b>if not state.exit:</b></i></div>
<div id="mb-reply" style="caret-color: rgb(51, 51, 51); color: #333333; font-family: "helvetica neue", helvetica, arial, "lucida grande", sans-serif; font-size: 14px;">
<i><b> return g(state)</b></i></div>
<div id="mb-reply" style="caret-color: rgb(51, 51, 51); color: #333333; font-family: "helvetica neue", helvetica, arial, "lucida grande", sans-serif; font-size: 14px;">
<br /></div>
<div id="mb-reply" style="caret-color: rgb(51, 51, 51); color: #333333; font-family: "helvetica neue", helvetica, arial, "lucida grande", sans-serif; font-size: 14px;">
And putting this in a function:</div>
<br />
<div style="-webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; caret-color: rgb(51, 51, 51); color: #333333; font-family: "helvetica neue", helvetica, arial, "lucida grande", sans-serif; font-size: 14px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-decoration: none; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px;">
<pre style="background-color: #f0f0f0; background-position: initial initial; background-repeat: initial initial; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; margin: 0px; overflow: auto; padding: 0px; text-align: left; width: 646.46875px;"><code style="color: black; word-wrap: normal;"> def compose2(f, g):
def run(x):
result_f = f(x)
if not result_f.exit:
return g(result_f)
else:
return result_f
return run </code></pre>
<pre style="background-color: #f0f0f0; background-position: initial initial; background-repeat: initial initial; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; margin: 0px; overflow: auto; padding: 0px; text-align: left; width: 646.46875px;"><code style="color: black; word-wrap: normal;">
</code></pre>
</div>
<div style="caret-color: rgb(51, 51, 51); color: #333333; font-family: "helvetica neue", helvetica, arial, "lucida grande", sans-serif; font-size: 14px;">
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"> <b>#compose n functions </b>
def compose(*functions):
return reduce(compose2, functions)
</code></pre>
</div>
<div id="mb-reply" style="caret-color: rgb(51, 51, 51); color: #333333; font-family: "helvetica neue", helvetica, arial, "lucida grande", sans-serif; font-size: 14px;">
<br /></div>
<div id="mb-reply" style="font-family: "helvetica neue", helvetica, arial, "lucida grande", sans-serif; font-size: 14px;">
<div style="color: #333333;">
<div id="mb-reply">
And now the final validation code:</div>
<div id="mb-reply">
<div>
<br /></div>
<div>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"> def validate_simplest_functional_composition(json):
initial_state = ValidationState(json=json, key="measurement",errors=[], exit=False)
composed_function = compose(</code></pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"><b style="font-family: arial;"> </b></code><b> </b><b>validate_key_exists</b>, </pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"><b style="font-family: arial;"> </b></code><b> </b><b>validate_not_null</b>,</pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"><b style="font-family: arial;"> </b></code><b> </b><b>validate_string_or_unicode</b>,</pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"><b style="font-family: arial;"> </b></code><b> </b><b>validate_not_empty_string</b>, </pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"><b style="font-family: arial;"> </b></code><b> </b><b>create_validate_length</b>(3, 10), </pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"><b style="font-family: arial;"> </b></code><b> </b><b>create_validate_not_in</b>(["archived","password"])) </pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"> final_state = composed_function(initial_state)
return final_state.errors
</code></pre>
</div>
</div>
</div>
<div id="mb-reply" style="color: #333333; line-height: 20px;">
<br /></div>
<div id="mb-reply" style="line-height: 20px;">
<div class="separator" style="clear: both; color: #333333; text-align: center;">
</div>
<div style="color: #333333;">
It is much better. It basically says: having an initial start of the system, run all these functions (validators) and at the end get a final state. Code: <a href="http://runnable.com/VNMhoTKLSn9Tm0GI/fighting-complexity-through-functional-composition-for-python">http://runnable.com/VNMhoTKLSn9Tm0GI/fighting-complexity-through-functional-composition-for-python</a></div>
<div id="mb-reply">
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
And it is:</div>
<div style="color: #333333;">
<br /></div>
<div class="separator" style="clear: both; color: #333333; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3wSMXMws0b_XMXdng3SU4N9OhXcrQIfsyvY6B8KghmWrZgSCMkhLSVDCWFiQZQ5bTz27sN67dgskbm6_A2nBGmPq0V845TBgO2l-fcXWC0FS7fMCJ-jw35_fBS_lceLFW_K19/s1600/Screen+Shot+2018-07-02+at+14.21.11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="416" data-original-width="389" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3wSMXMws0b_XMXdng3SU4N9OhXcrQIfsyvY6B8KghmWrZgSCMkhLSVDCWFiQZQ5bTz27sN67dgskbm6_A2nBGmPq0V845TBgO2l-fcXWC0FS7fMCJ-jw35_fBS_lceLFW_K19/s320/Screen+Shot+2018-07-02+at+14.21.11.png" width="299" /></a></div>
<div class="separator" style="clear: both; color: #333333; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; color: #333333; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; color: #333333; text-align: center;">
<br /></div>
<h2 style="color: #333333;">
5. So where can I use this?</h2>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
If you're a backend developer, you can use it on a server (python example):</div>
<div style="color: #333333;">
<br /></div>
<pre style="background-color: white; caret-color: rgb(0, 0, 0); color: black; font-family: Menlo; font-size: 9pt;"><pre style="font-family: Menlo; font-size: 9pt;"><span style="color: #0000b2;">@mod.route</span>(<span style="color: green; font-weight: bold;">'/api/1/save/<typ>'</typ></span>, <span style="color: #660099;">methods</span>=[<span style="color: green; font-weight: bold;">'POST'</span>])
<span style="color: #0000b2;">@pi_service</span>()
<span style="color: navy; font-weight: bold;">def </span>generic_save(version=<span style="color: blue;">1</span>, typ=<span style="color: navy;">None</span>):</pre>
<pre style="font-family: Menlo; font-size: 9pt;"> composed_func = <span style="font-size: 9pt;">compose_list(</span><pre style="font-family: Menlo; font-size: 9pt;"> [
can_write(<span style="color: green; font-weight: bold;">"tags"</span>),<span style="color: grey; font-style: italic;">
</span><span style="color: grey; font-style: italic;"> </span>change(<span style="color: green; font-weight: bold;">"json"</span>, request.json),
change(<span style="color: green; font-weight: bold;">"session"</span>, get_session()),
change(<span style="color: green; font-weight: bold;">"type"</span>, get_pi_type(typ)),
change(<span style="color: green; font-weight: bold;">"object"</span>, <span style="color: navy;">None</span>),
change(<span style="color: green; font-weight: bold;">"transformer"</span>, get_pi_transformer(typ)),
get_database_object,
transform_from_json,
save_database_object,
index_tag_or_tag_group,
pi_transform_to_json,
])</pre>
<span style="color: navy; font-weight: bold;">return </span><span style="font-size: 9pt;">composed_func({})</span></pre>
</pre>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
or in a PDF generating server, written in Clojure over Apache Batik (using transducers but that's another discussion)</div>
<div style="color: #333333;">
<br /></div>
<div class="separator" style="clear: both; color: #333333; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOTjqYM0kMh3XAtY6paiT5yk29xsJUQBKeVISr14-iar5FTihED3f3WYN45ozVvuGQZdYJ3MnR_roYWILKMof4mHHv2cGRdgPsMk4hFlO-3V-1S6JEOA0K3gENwQpwzH5guqi2/s1600/Screen+Shot+2018-07-02+at+14.46.57.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="303" data-original-width="543" height="221" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOTjqYM0kMh3XAtY6paiT5yk29xsJUQBKeVISr14-iar5FTihED3f3WYN45ozVvuGQZdYJ3MnR_roYWILKMof4mHHv2cGRdgPsMk4hFlO-3V-1S6JEOA0K3gENwQpwzH5guqi2/s400/Screen+Shot+2018-07-02+at+14.46.57.png" width="400" /></a></div>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
You could use javascript promises for piping, with React, if you're a front-end developer. The state of the system the model (immutable) and rendering is done views.render:</div>
<div style="color: #333333;">
<br /></div>
<pre style="background-color: white; caret-color: rgb(0, 0, 0); color: black; font-family: Menlo; font-size: 9pt;"><pre style="font-family: Menlo; font-size: 9pt;">StoryboardController.prototype.move_point_by = <span style="color: navy; font-weight: bold;">function</span>(page_object, point_index, dx, dy) {
pi.startWith(model,<span style="color: green; font-weight: bold;">"MOVE POINT BY"</span>)
.then(<span style="color: navy; font-weight: bold;">function </span>move_point_by(state){
pi.info(<span style="color: green; font-weight: bold;">"move point by"</span>, page_object, point_index, dx, dy);
<span style="color: navy; font-weight: bold;">var </span>cursor = get_selected_layer_cursor(state) + <span style="color: green; font-weight: bold;">".children" </span>+ find_cursor_pageobject(page_object, state);
<span style="color: navy; font-weight: bold;">if </span>(cursor) {
<span style="color: navy; font-weight: bold;">var </span>point_cursor = cursor+<span style="color: green; font-weight: bold;">".points["</span>+point_index+<span style="color: green; font-weight: bold;">"]"</span>;
<span style="color: navy; font-weight: bold;">var </span>point = pi.pi_value(state, point_cursor);
<span style="color: navy; font-weight: bold;">var </span>changes = {};
<span style="color: navy; font-weight: bold;">var </span>nx=point.x+dx;
<span style="color: navy; font-weight: bold;">var </span>ny=point.y+dy;
changes[point_cursor+<span style="color: green; font-weight: bold;">".x"</span>]=nx;
changes[point_cursor+<span style="color: green; font-weight: bold;">".y"</span>]=ny;
state = pi.pi_change_multi(state, changes);
<span style="color: navy; font-weight: bold;">return </span>resize_shape(state, cursor);
}
<span style="color: navy; font-weight: bold;">return </span>state;
})
.then(views.render)
.then(swap_model)
.then(REST.try_save_page)
}</pre>
</pre>
<pre style="background-color: white; caret-color: rgb(0, 0, 0); color: black; font-family: Menlo; font-size: 9pt;">
</pre>
<pre style="background-color: white; caret-color: rgb(0, 0, 0); color: black; font-family: Menlo; font-size: 9pt;">or</pre>
<pre style="background-color: white; caret-color: rgb(0, 0, 0); color: black; font-family: Menlo; font-size: 9pt;">
</pre>
<pre style="background-color: white; caret-color: rgb(0, 0, 0); color: black; font-family: Menlo; font-size: 9pt;">
</pre>
<pre style="background-color: white; caret-color: rgb(0, 0, 0); color: black; font-family: Menlo; font-size: 9pt;"><span style="font-size: 9pt;"> </span></pre>
<div style="color: #333333;">
or in Clojurescript, where the state of the system is an atom (model) and every time it changes, the view is rerendered:</div>
<div style="color: #333333;">
<br /></div>
<div class="separator" style="clear: both; color: #333333; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgy5cSl9EM2dZm7E_9ykQFA8XqqGyRHAdOVPkCL4RmeflL_QfWSHvtwrFEGd4NUjFvHVrAUXJKCbd1mErfmOgR2cdGZ4FwKvOaONvOWb1G6G-4bjXhHaB8QQJITdtoKJ1WkZwPJ/s1600/Screen+Shot+2018-07-02+at+14.37.46.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="186" data-original-width="860" height="137" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgy5cSl9EM2dZm7E_9ykQFA8XqqGyRHAdOVPkCL4RmeflL_QfWSHvtwrFEGd4NUjFvHVrAUXJKCbd1mErfmOgR2cdGZ4FwKvOaONvOWb1G6G-4bjXhHaB8QQJITdtoKJ1WkZwPJ/s640/Screen+Shot+2018-07-02+at+14.37.46.png" width="640" /></a></div>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
<br /></div>
<h2>
6. Conclusion</h2>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
Using this model, code is easier to understand, debug, change, extend. Why: </div>
<div style="color: #333333;">
- all the data is in a place</div>
<div style="color: #333333;">
<br /></div>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;">initial_state = ValidationState(json=json, key="measurement",errors=[], exit=False) </code></pre>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
- functions are simple </div>
<div style="color: #333333;">
<br /></div>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"> def validate_key_exists(state):
print validate_key_exists.__name__,state
if not key_exists(state.json,state.key):
return state._replace(errors = state.errors+["{0} cannot be missing".format(state.key)])._replace(exit=True)
return state </code></pre>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
- intermediary states can be easily debugged</div>
<div style="color: #333333;">
<br /></div>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"> composed_function = compose(</code></pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"><b style="font-family: arial;"> </b></code><b> </b><b>validate_key_exists</b>, </pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"><b style="font-family: arial;"> </b></code><b> </b><b>validate_not_null</b>,</pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"> <b><span style="color: red;">debug</span></b>,</pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"><b style="font-family: arial;"> </b></code><b> </b><b>validate_string_or_unicode</b>,</pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"><b style="font-family: arial;"> </b></code><b> </b><b>validate_not_empty_string</b>, </pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"><b style="font-family: arial;"> </b></code><b> </b><b>create_validate_length</b>(3, 10), </pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"><b style="font-family: arial;"> </b></code><b> </b><b>create_validate_not_in</b>(["archived","password"])) </pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"> final_state = composed_function(initial_state)
</code></pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;">
</code></pre>
<pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;">
</code></pre>
<div style="color: #333333;">
<code style="word-wrap: normal;"><pre style="background-color: #f0f0f0; border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; width: 646.46875px;"><code style="word-wrap: normal;"> def debug(state):
print state.json, state.key, state.errors, state.exit
return state </code></pre>
</code></div>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
- data changes flow in a single direction</div>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
In part two: robustness, we'll see how we could also make the code robust, by making the code run transactionally same as databases: either all runs or none and the state gets reverted to the previous one. </div>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
<br /></div>
<div style="color: #333333;">
<br /></div>
</div>
</div>
</div>
<div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-40649284832315952272017-04-13T16:47:00.002+03:002017-04-13T16:47:54.038+03:00What is wrong with static typing in JavaScript and how clojure.spec solves the problem (Part 2)<h3>
<span style="font-size: large;">The problem</span></h3>
<br />
The main problem with dynamic typing seems to be fear that the wrong type of data will end up in the wrong place (function input for instance).<br />
<br />
However it turns out static typing is pretty useless.<br />
<h4>
<b>Example 1: Simple types Age</b></h4>
<br />
Let's say you have to record an age for a person in a variable, or have it as a parameter in a function.<br />
<br />
You would do something like:<br />
<br />
int age = 25;<br />
<br />
or<br />
<br />
function something(int age...)<br />
<br />
Someone pretended even that static typing shows intent. Now the only thing that is in here, is that it will be an int. There is nothing protecting the age from being either negative (-2) or too big (4000, it might work if you're talking about the age of the pyramids). So it is not intent, it is just int, not further protection, so pretty much useless.<br />
<br />
<b>Solution 1</b><br />
<br />
In Clojure REPL using spec <span style="background-color: white; font-family: "menlo"; font-size: 14px;">(require '[clojure.spec :as s]) </span>we define a spec saying we want a natural int (positive int) and it should be smaller then let's say 150:<br />
<br />
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
(s/def ::age (s/and nat-int? (fn [x] (< x 150))))</div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<br /></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
Now:</div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<br /></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
user=> (s/valid? ::age "a")</div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">false</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/valid? ::age -12)</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">false</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/valid? ::age true)</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">false</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/valid? ::age 1.21)</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">false</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/valid? ::age 4000)</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">false</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/valid? ::age -2)</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">false</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/valid? ::age 0)</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">true</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/valid? ::age 12)</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">true</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/valid? ::age 29)</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">true</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/valid? ::age 99)</span></div>
<br />
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">true</span></div>
<div>
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<br />
<h4>
<b>Example 2: Composed types: Person</b></h4>
<br />
Let's say we get through a web call a json like:<br />
<br />
{<br />
"id":6,<br />
"name":"Dan",<br />
"age":28<br />
}<br />
<br />
Usually people would create a class<br />
<br />
class Person<br />
{<br />
int id;<br />
string name;<br />
int age;<br />
}<br />
<br />
So we have the same problems, for instance name might be null, or age might be negative.<br />
<br />
Then in modern apps, you get json and you send json, so you need to be able to serialize and deserialize to json this class. What happens if one of the parameters is not comform or missing?<br />
<br />
<b>Solution 2</b><br />
<br />
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">(s/def ::id nat-int?)</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"></span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">(s/def ::name string?)</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"></span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">(s/def ::person (s/keys :req-un [::id ::name ::age]))</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; font-size: 14px; line-height: normal;">
<span style="font-family: inherit; font-variant-ligatures: no-common-ligatures;">Now:</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/valid? ::person {:id 1, :name "Adi"})</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">false</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/valid? ::person {:id 1, :name "Adi" :age -1})</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">false</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/valid? ::person {:id 1, :name "Adi" :age 40})</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
</div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">true</span></div>
<br />
What's even cooler, is that if it isn't valid, you can get an explanation:<br />
<h4>
<div style="background-color: white; font-family: Menlo; font-size: 14px; font-weight: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/explain ::person {:id 1, :name "Adi" :age -1})</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; font-weight: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">In: [:age] val: -1 fails spec: :user/age at: [:age] predicate: nat-int?</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; font-weight: normal; line-height: normal;">
<br /></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; font-weight: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/explain ::person {:id 1})</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; font-weight: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">val: {:id 1} fails spec: :user/person predicate: (contains? % :name)</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; font-weight: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">val: {:id 1} fails spec: :user/person predicate: (contains? % :age)</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; font-weight: normal; line-height: normal;">
<br /></div>
</h4>
<h4>
Example 3: Hierarchies </h4>
<br />
What if you have:<br />
<br />
{<br />
"id": 6,<br />
"name": "Dan",<br />
"age": 28,<br />
"children": [{<br />
"id": 7,<br />
"name": "Alex",<br />
"age": 5<br />
}<br />
]<br />
}<br />
<br />
When the first object is a parent, in a school and he must have at least one child? You can enforce the relationship by writing a function and in the constructor, but then you also need to change the serialization/deserialization from json to enforce the rules, and of course you will write more code and you will forget to check it once, and there will be a bug.<br />
<br />
And the most common problem of our times. The json is like:<br />
<br />
{<br />
"id": 11,<br />
"name": "Maria",<br />
"age": 95,<br />
"children": [{<br />
"id": 5,<br />
"name": "Elena",<br />
"age": 28,<br />
"children": [{<br />
"id": 6,<br />
"name": "Dan",<br />
"age": 60,<br />
"children": [{<br />
"id": 7,<br />
"name": "Alex",<br />
"age": 5<br />
}],<br />
{<br />
"id": 9,<br />
"name": "Alina",<br />
"age": 32,<br />
"children": [{<br />
"id": 121,<br />
"name": "Luiza",<br />
"age": 0<br />
}]<br />
}<br />
}],<br />
<br />
{<br />
"id": 23,<br />
"name": "Petru",<br />
"age": 70,<br />
"children": [{<br />
"id": 4,<br />
"name": "Adrian",<br />
"children": [{<br />
"id": 45,<br />
"name": "Denis",<br />
"age": 12<br />
}],<br />
]<br />
}]<br />
}]<br />
}<br />
<br />
You have a single error but where? (Maria / Petru / Adrian - missing "age"). It is not only hard to validate it but it is hard to show explicitly where the error occurred.<br />
<br />
<b>Solutions</b><br />
<br />
(s/+ says that there will be a collection of person's with a minimum of 1:<br />
<br />
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">(s/def ::children (s/+ ::person))</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"></span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">(s/def ::parent (s/keys :req-un [::id ::name ::age ::children]))</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; font-size: 14px; line-height: normal;">
<span style="font-family: inherit;">Now:</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<br /></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/valid? ::parent {:id 1, :name "Adi" :age 40 :children []})</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
</div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">false</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/valid? ::parent {:id 1, :name "Adi" :age 40 :children [{:id 1, :name "Adi" :age 40}]})</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">true</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/valid? ::parent {:id 1, :name "Adi" :age 40 :children [{:id 1, :name "Adi" :age 40}, {:id 2, :name "Dan" :age 20}]})</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">
</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">true</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; font-size: 14px; line-height: normal;">
<span style="font-family: inherit; font-variant-ligatures: no-common-ligatures;">and if we don't have children:</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">(s/explain ::parent {:id 1, :name "Adi" :age 40 :children []})</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">In: [:children] val: () fails spec: :user/person at: [:children] predicate: :user/person, Insufficient input</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">
</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<br /></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; font-size: 14px; line-height: normal;">
<span style="font-family: inherit; font-size: small;">Even cooler is that you can check relations between data, like if the children are younger then their parents: </span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-family: -webkit-standard; font-size: small;"><br /></span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">(defn parent-older-than-children? [parent] (reduce #(or %1 %2) (map #(> (:age parent) (:age %)) (:children parent))))</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; font-size: 14px; line-height: normal;">
<span style="font-family: inherit; font-variant-ligatures: no-common-ligatures;">we redefine the ::parent</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"></span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">(s/def ::parent (s/and (s/keys :req-un [::id ::name ::age ::children]) parent-older-than-children?))</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/valid? ::parent {:id 1, :name "Adi" :age 40 :children [{:id 1, :name "Adi" :age 50}]})</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">false</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
user=> (s/explain ::parent {:id 1, :name "Adi" :age 40 :children [{:id 1, :name "Adi" :age 50}]})</div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal; min-height: 16px;">
<span style="font-variant-ligatures: no-common-ligatures;"></span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">
</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">val: {:id 1, :name "Adi", :age 40, :children [{:id 1, :name "Adi", :age 50}]} fails spec: :user/parent predicate: parent-older-than-children?</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">user=> (s/valid? ::parent {:id 1, :name "Adi" :age 45 :children [{:id 1, :name "Adi" :age 25}, {:id 2, :name "Dan" :age 20}]})</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">
</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 14px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">true</span></div>
<div>
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<br />
<h4>
<span style="font-weight: normal;">In Part 3 we will look at functions and one more thing ...</span></h4>
<div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-62310215565420725342017-03-30T14:29:00.001+03:002017-03-30T14:33:13.619+03:00What is wrong with static typing in JavaScript and how clojure.spec solves the problem (Part 1)<h3>
<span style="font-size: large;">The problem</span></h3>
<br />
The main problem with dynamic typing seems to be fear that the wrong type of data will end up in the wrong place (function input for instance).<br />
<br />
However it turns out static typing is pretty useless.<br />
<h4>
<b>Example 1: Simple types Age</b></h4>
<br />
Let's say you have to record an age for a person in a variable, or have it as a parameter in a function.<br />
<br />
You would do something like:<br />
<br />
int age = 25;<br />
<br />
or<br />
<br />
function something(int age...)<br />
<br />
Someone pretended even that static typing shows intent. Now the only thing that is in here, is that it will be an int. There is nothing protecting the age from being either negative (-2) or too big (4000, it might work if you're talking about the age of the pyramids). So it is not intent, it is just int, not further protection, so pretty much useless.<br />
<br />
<h4>
<b>Example 2: Composed types: Person</b></h4>
<br />
Let's say we get through a web call a json like:<br />
<br />
{<br />
"id":6,<br />
"name":"Dan",<br />
"age":28<br />
}<br />
<br />
Usually people would create a class<br />
<br />
class Person<br />
{<br />
int id;<br />
string name;<br />
int age;<br />
}<br />
<br />
So we have the same problems, for instance name might be null, or age might be negative.<br />
<br />
Then in modern apps, you get json and you send json, so you need to be able to serialize and deserialize to json this class. What happens if one of the parameters is not comform or missing?<br />
<br />
<h4>
Example 3: Hierarchies </h4>
<br />
What if you have:<br />
<br />
{<br />
"id": 6,<br />
"name": "Dan",<br />
"age": 28,<br />
"children": [{<br />
"id": 7,<br />
"name": "Alex",<br />
"age": 5<br />
}<br />
]<br />
}<br />
<br />
When the first object is a parent, in a school and he must have at least one child? You can enforce the relationship by writing a function and in the constructor, but then you also need to change the serialization/deserialization from json to enforce the rules, and of course you will write more code and you will forget to check it once, and there will be a bug.<br />
<br />
And the most common problem of our times. The json is like: <br />
<br />
{<br />
"id": 11,<br />
"name": "Maria",<br />
"age": 95,<br />
"children": [{<br />
"id": 5,<br />
"name": "Elena",<br />
"age": 28,<br />
"children": [{<br />
"id": 6,<br />
"name": "Dan",<br />
"age": 60,<br />
"children": [{<br />
"id": 7,<br />
"name": "Alex",<br />
"age": 5<br />
}],<br />
{<br />
"id": 9,<br />
"name": "Alina",<br />
"age": 32,<br />
"children": [{<br />
"id": 121,<br />
"name": "Luiza",<br />
"age": 0<br />
}]<br />
}<br />
}],<br />
<br />
{<br />
"id": 23,<br />
"name": "Petru",<br />
"age": 70,<br />
"children": [{<br />
"id": 4,<br />
"name": "Adrian",<br />
"children": [{<br />
"id": 45,<br />
"name": "Denis",<br />
"age": 12<br />
}],<br />
]<br />
}]<br />
}]<br />
}<br />
<br />
You have a single error but where? (Maria / Petru / Adrian - missing "age"). It is not only hard to validate it but it is hard to show explicitly where the error occurred.<br />
<br />
<h4>
<b>Example 5: Functions</b></h4>
<br />
Let's try to find a string in another string. A function would be like:<br />
<br />
int indexOf(string search, string what) ...<br />
<br />
Which tells you that you will get an int, and you can pass two strings. First what if the strings are null? What if both strings are empty "", "". What if the result for "ab", "b" is 1248764 or -12. According to the function definition it is an int, and should be valid.<br />
<br />
<h4>
<b>Example 6. Unit testing</b></h4>
<br />
To ensure the function above is well specified, we also use unit testing. Problem 1: unit testing doesn't care if it is static or dynamic typing. Problem 2 is very unit testing specific: Having enough tests, maintaining them when the function changes (like adding a new parameter), not enough testing, or too optimistic testing.<br />
<br />
<br />
The solution proposed by Clojure.spec will be shown in part 2. And it is pretty cool! :)<div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-81338330135771621732017-03-17T14:37:00.006+02:002017-03-17T14:37:36.367+02:00Sorting maps in Clojure<b>Problem </b><br />
<br />
If you like to keep data in maps in ClojureScript to be able to access it fast, but also need sorting, maybe you should read this.<br />
<br />
<b>Cause</b><br />
<br />
Let's say you have a map like:<br />
<br />
<span style="color: blue;">(def a {:0 0, :1 1, :2 2, :3 3, :4 4, :5 5, :6 6, :7 7}) </span><br />
<span style="color: blue;"> (vals a)
would return:
(0 1 2 3 4 5 6 7) </span><br />
<br />
But what about<br />
<br />
<span style="color: blue;">(def a {:0 0, :1 1, :2 2, :3 3, :4 4, :5 5, :6 6, :7 7, :8 8}) </span><br />
<br />
where<br />
<br />
<span style="color: blue;">(vals b)
</span>
returns:
<span style="color: blue;">(6 7 4 5 1 0 3 2 8) </span><br />
<br />
The trick is how data is represented internally. If the number of pairs is less then 8 then
<span style="color: blue;">(type a)</span> is a clojure.lang.PersistentArrayMap
but
<span style="color: blue;">(type b)</span> is a clojure.lang.PersistentHashMap
which is optimized for access, but loses order as a compromise.<br />
<br />
If we generate our maps using a function:<br />
<br />
<span style="color: blue;"> (defn gen [x]
(doall (map (fn [x] [(keyword (str x)) x]) (range x))))
try: </span><br />
<span style="color: blue;"><br /></span>
<span style="color: blue;"> (->> (gen 8) </span><br />
<span style="color: blue;"> (into {}) </span><br />
<span style="color: blue;"> type
) </span><br />
<span style="color: blue;"><br /></span>
<span style="color: blue;"> (->> (gen 9) </span><br />
<span style="color: blue;"> (into {}) </span><br />
<span style="color: blue;"> type ) </span><br />
<br />
and you'll see for yourself.<div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-85178107787116697122016-10-19T10:51:00.001+03:002016-10-19T10:51:19.561+03:00Learn clojure.spec (cljs,spec) interactivelyThere is one fantastic tool called klipse which allows you to run Clojurescript interactively in the browser. It is even more helpful when it allows you to learn something like clojure.spec by examples that actually run in realtime and which you can change and see how everything works: <a href="http://blog.klipse.tech/clojure/2016/05/30/spec.html">http://blog.klipse.tech/clojure/2016/05/30/spec.html</a><div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-49115654224307293772015-12-18T23:09:00.001+02:002015-12-18T23:09:16.721+02:00Clojurescript/Reagent: How to start in 1 minute<br />
<h3>
What is a great development environment for web applications?</h3>
<br />
One that:<br />
<br />
<ol>
<li> can be started and configured extremely easy</li>
<li> allows instant feedback</li>
<li> can be done in a great programming language </li>
<li> doesn't need expensive tools</li>
</ol>
<br />
<br />
So Clojurescript. For clojure/clojurescript projects you need one tool installed: Leiningen, which will be used from the command line. (install from here. You need java installed before)<br />
<br />
<h4>
1. can be started and configured extremely easy</h4>
<br />
Create a new project<br />
<br />
lein new figwheel hello_world -- --reagent<br />
<br />
And start it:<br />
<br />
cd hello_world<br />
<br />
lein figwheeel<br />
<br />
Now, go in your browser to localhost:3449<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiaxbAOQTbz7L9EzKmNAkUFmJMEzWq48zOpNYlB8FbeeQjAtLvW6u2prwb3L54kxu4ZDypuH10tuhhQ9vgWTk05-4t9Vj1r-rT2EX8Ffv3w00MZ8asj1ikrcOW8Tqc-ANQmd8_/s1600/Screen+Shot+2015-12-18+at+22.49.22.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="302" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiaxbAOQTbz7L9EzKmNAkUFmJMEzWq48zOpNYlB8FbeeQjAtLvW6u2prwb3L54kxu4ZDypuH10tuhhQ9vgWTk05-4t9Vj1r-rT2EX8Ffv3w00MZ8asj1ikrcOW8Tqc-ANQmd8_/s640/Screen+Shot+2015-12-18+at+22.49.22.png" width="640" /></a></div>
<br />
<br />
It is already there!!!<br />
<br />
<h4>
2. instant feedback</h4>
<br />
Open the code using some tool, preferably LightTable and do this change in src/hello_world/core.cljs in the hello_world component:<br />
<br />
{:style {:color "red"}}<br />
<br />
Save the file.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZvOeoxOClbVrH1UPOoe7PaIQDpRv3FTsSZQegVhLB8bqXR72eSFPtckzr4IpDYTa6oYPbpxMoW9T7ofgQ9jvJTXkveepEsXv0KmmWmQxg1nODTGyl6Txw1p6luBvy9Zkv0V85/s1600/Screen+Shot+2015-12-18+at+23.01.54.png" imageanchor="1"><img border="0" height="282" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZvOeoxOClbVrH1UPOoe7PaIQDpRv3FTsSZQegVhLB8bqXR72eSFPtckzr4IpDYTa6oYPbpxMoW9T7ofgQ9jvJTXkveepEsXv0KmmWmQxg1nODTGyl6Txw1p6luBvy9Zkv0V85/s640/Screen+Shot+2015-12-18+at+23.01.54.png" width="640" /></a><br />
<br />
It is already in the browser!!<br />
<br />
now, let's start that in console.<br />
<br />
<br />
<div class="p1">
<span class="s1">(in-ns 'hello_world.core)</span></div>
<div class="p1">
<span class="s1"><br /></span></div>
<div class="p1">
<span class="s1">Now let's change the text to Hello Dan!</span></div>
<div class="p1">
<span class="s1"><br /></span></div>
<div class="p1">
<span class="s1">
</span></div>
<div class="p1">
<span class="s1">(swap! app-state assoc :text "Hello Dan!")</span></div>
<br />
<br />
Boom!<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeINedrPk-ls8dGMqdttj-KFQL4_Xc4enW-lb13GW5XGWy2WhR9hIXE2BzYQRopA2rj5eQQ5YXWdn-SrSCY5Wxw_R3_hprm9P_BMoztQkF71-cP6EUhA7ZbJlFyCD6NJThcyuo/s1600/Screen+Shot+2015-12-18+at+23.05.17.png" imageanchor="1"><img border="0" height="342" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeINedrPk-ls8dGMqdttj-KFQL4_Xc4enW-lb13GW5XGWy2WhR9hIXE2BzYQRopA2rj5eQQ5YXWdn-SrSCY5Wxw_R3_hprm9P_BMoztQkF71-cP6EUhA7ZbJlFyCD6NJThcyuo/s640/Screen+Shot+2015-12-18+at+23.05.17.png" width="640" /></a><br />
<br />
It is already in the browser. Figwheel takes care of all that!<br />
<br />
<br />
Now 3 and 4 are answered by clojurescript which is based on the great programming language clojure. We're also using Reagent (clojurescript library on top of Facebook React) and figwheel which allows all the instant feedback stuff.<br />
<br />
<br />
<br /><div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-49462605808992696572015-10-17T19:57:00.002+03:002019-10-16T10:27:34.959+03:00REST api using Clojure and MySql. Is Clojure the most productive, robust language on the planet?Updated 2019 code: <a href="https://gitlab.com/danbunea/production-ready-clojure">https://gitlab.com/danbunea/production-ready-clojure</a><br />
<br />
In my life, I have written a lot of web applications, using Java, then .NET, PHP and lately REST API;s using Python. I thought there cannot be anything to match Python productivity using Flask and SqlAlchemy until today.<br />
<br />
<h4>
Making a REST api in Clojure using Ring/Compojure and SqlKorma </h4>
<div>
There is a great package manager called Leiningen (<a href="http://leiningen.org/">http://leiningen.org</a>). To create a new web application, in a Terminal:<br />
<br />
<div class="p1">
<span class="s1"><b>> lein new compojure todoapp2</b></span></div>
<div class="p1">
<b><span class="s2">></span><span class="s1"> cd todoapp2</span></b><br />
<b><span class="s1"><br /></span></b>
<span class="s1"><i>Update 2019</i></span><br />
<b><span class="s1"><br /></span></b>
<div style="font-family: Menlo; font-size: 13px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><b>> lein new compojure-api asado +clojure-test</b></span></div>
<div style="font-family: Menlo; font-size: 13px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><b>> cd asado</b></span></div>
</div>
<div class="p1">
<span class="s1"><br /></span></div>
<div class="p1">
<span class="s1">Now you have the skeleton application with a pretty known structure. Now we need to configure which packages will be used, editing package.clj:</span></div>
<div class="p1">
<span class="s1"><br /></span></div>
<div class="p1">
<span style="color: blue;">(defproject todoapp2 "0.1.0-SNAPSHOT"</span></div>
<div class="p1">
<span style="color: blue;"> :description "FIXME: write description"</span></div>
<div class="p1">
<span style="color: blue;"> :url "http://example.com/FIXME"</span></div>
<div class="p1">
<span style="color: blue;"> :min-lein-version "2.0.0"</span></div>
<div class="p1">
<span style="color: blue;"> :dependencies [[org.clojure/clojure "1.6.0"]</span></div>
<div class="p1">
<span style="color: blue;"> [compojure "1.3.1"]</span></div>
<div class="p1">
<b><span style="color: blue;"> [ring/ring-core "1.3.2"]</span></b></div>
<div class="p1">
<b><span style="color: blue;"> [ring/ring-json "0.3.1"]</span></b></div>
<div class="p1">
<b><span style="color: blue;"> [ring/ring-defaults "0.1.4"]</span></b></div>
<div class="p1">
<b><span style="color: blue;"> [korma "0.3.0-RC5"]</span></b></div>
<div class="p1">
<b><span style="color: blue;"> [mysql/mysql-connector-java "5.1.6"]]</span></b></div>
<div class="p1">
<span style="color: blue;"> :plugins [[lein-ring "0.8.13"]]</span></div>
<div class="p1">
<span style="color: blue;"> :ring {:handler todoapp2.handler/app}</span></div>
<div class="p1">
<span style="color: blue;"> :profiles</span></div>
<div class="p1">
<span style="color: blue;"> {:dev {:dependencies [[javax.servlet/servlet-api "2.5"]</span></div>
<div class="p1">
<span style="color: blue;"><span class="s1"></span></span></div>
<div class="p1">
<span style="color: blue;"> [ring-mock "0.1.5"]]}})</span><br />
<span style="color: blue;"><br /></span>
<span style="color: blue;">Update 2019:</span><br />
<span style="color: blue;"><br /></span>
<span style="color: blue;"></span><br />
<pre style="background-color: white; font-family: Menlo; font-size: 9pt;"><i>(<span style="color: navy; font-weight: bold;">defproject </span>asado <span style="color: green; font-weight: bold;">"0.1.0-SNAPSHOT"</span><span style="color: green; font-weight: bold;"> </span><span style="color: #660e7a;">:description </span><span style="color: green; font-weight: bold;">"FIXME: write description"</span><span style="color: green; font-weight: bold;"> </span><span style="color: #660e7a;">:dependencies </span>[[org.clojure/clojure <span style="color: green; font-weight: bold;">"1.10.0"</span>]
[metosin/compojure-api <span style="color: green; font-weight: bold;">"2.0.0-alpha30"</span>]
[ring/ring <span style="color: green; font-weight: bold;">"1.6.3"</span>]
[compojure <span style="color: green; font-weight: bold;">"1.6.1"</span>]
[manifold <span style="color: green; font-weight: bold;">"0.1.8"</span>]
[metosin/spec-tools <span style="color: green; font-weight: bold;">"0.9.2"</span>]
<span style="color: grey;">;database</span><span style="color: grey;"> </span>[korma <span style="color: green; font-weight: bold;">"0.4.3"</span>]
[mysql/mysql-connector-java <span style="color: green; font-weight: bold;">"8.0.12"</span>]
]
<span style="color: #660e7a;">:ring </span>{<span style="color: #660e7a;">:handler </span>asado.handler/app}
<span style="color: #660e7a;">:uberjar-name </span><span style="color: green; font-weight: bold;">"server.jar"</span><span style="color: green; font-weight: bold;"> </span><span style="color: #660e7a;">:profiles </span>{<span style="color: #660e7a;">:dev </span>{<span style="color: #660e7a;">:dependencies </span>[[javax.servlet/javax.servlet-api <span style="color: green; font-weight: bold;">"3.1.0"</span>]
[ring/ring-mock <span style="color: green; font-weight: bold;">"0.3.2"</span>]]
<span style="color: #660e7a;">:plugins </span>[[lein-ring <span style="color: green; font-weight: bold;">"0.12.5"</span>]]}})</i></pre>
</div>
<div class="p1">
<br /></div>
<div class="p1">
Now to add all dependencies:</div>
<div class="p1">
<br /></div>
<div class="p1">
<b>> lein deps </b></div>
<div class="p1">
<br /></div>
<div class="p1">
Basically we need the json package, sqlkorma and the mysql JDBC driver. All installed. </div>
<div class="p1">
<br /></div>
<div class="p1">
In MySql we'll create a database todo, where we create a table items, with id, title (varchar), is_complete (tinyint).<br />
<br />
<span style="color: blue;">CREATE TABLE `items` (</span><br />
<span style="color: blue;"> `id` int(5) NOT NULL AUTO_INCREMENT,</span><br />
<span style="color: blue;"> `title` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',</span><br />
<span style="color: blue;"> `is_complete` tinyint(1) NOT NULL DEFAULT '0',</span><br />
<span style="color: blue;"> PRIMARY KEY (`id`)</span><br />
<span style="color: blue;">) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;</span></div>
<div class="p1">
<br /></div>
<div class="p1">
Now let's create a database.clj file where we configure the database connection details:</div>
<div class="p1">
<br /></div>
<div class="p1">
<span style="color: blue;">(ns todoapp2.database</span></div>
<div class="p1">
<span style="color: blue;"> (:require [korma.db :as korma]))</span></div>
<div class="p1">
<span style="color: blue;"><br /></span></div>
<div class="p1">
<span style="color: blue;">(def db-connection-info (korma/mysql </span></div>
<div class="p1">
<span style="color: blue;"> {:classname "com.mysql.jdbc.Driver"</span></div>
<div class="p1">
<span style="color: blue;"> :subprotocol "mysql"</span></div>
<div class="p1">
<span style="color: blue;"> :user "root"</span></div>
<div class="p1">
<span style="color: blue;"> :subname "//localhost:3306/todo"}))</span></div>
<div class="p1">
<span style="color: blue;"><br /></span></div>
<div class="p1">
<span style="color: blue;">; set up korma</span></div>
<div class="p1">
<span style="color: blue;">(korma/defdb db db-connection-info)</span><br />
<span style="color: blue;"><br /></span>
<span style="color: blue;"><i>Update 2019:</i></span><br />
<span style="color: blue;"><i><br /></i></span>
<span style="color: blue;"></span><br />
<pre style="background-color: white; font-family: Menlo; font-size: 9pt;"><i>(<span style="color: navy; font-weight: bold;">ns </span>asado.database
(<span style="color: #660e7a;">:require </span>[korma.db <span style="color: #660e7a;">:as </span>korma]))
(<span style="color: navy; font-weight: bold;">def </span>db-connection-info (<span style="color: navy; font-weight: bold;">korma/mysql</span><span style="color: navy; font-weight: bold;"> </span>{<span style="color: #660e7a;">:classname </span><span style="color: green; font-weight: bold;">"com.mysql.cj.jdbc.Driver"</span><span style="color: green; font-weight: bold;"> </span><span style="color: #660e7a;">:subprotocol </span><span style="color: green; font-weight: bold;">"mysql"</span><span style="color: green; font-weight: bold;"> </span><span style="color: #660e7a;">:user </span><span style="color: green; font-weight: bold;">"root"</span><span style="color: green; font-weight: bold;">
</span><span style="color: green; font-weight: bold;"> </span><span style="color: #660e7a;">:subname </span><span style="color: green; font-weight: bold;">"//localhost:3307/todo"</span>}))
<span style="color: grey;">; set up korma</span>(<span style="color: navy; font-weight: bold;">korma/defdb </span>db db-connection-info)</i></pre>
</div>
<div class="p1">
<br /></div>
<div class="p1">
Now let's write the database access functions, in a new file: query.clj</div>
<div class="p1">
<br /></div>
<div class="p1">
<span style="color: blue;">(ns todoapp2.query</span></div>
<div class="p1">
<span style="color: blue;"> (:require [todoapp2.database]</span></div>
<div class="p1">
<span style="color: blue;"> [korma.core :refer :all]))</span></div>
<div class="p1">
<span style="color: blue;"><br /></span></div>
<div class="p1">
<span style="color: blue;">(defentity items)</span></div>
<div class="p1">
<span style="color: blue;"><br /></span></div>
<div class="p1">
<span style="color: blue;">(defn get-todos []</span></div>
<div class="p1">
<span style="color: blue;"> (select items))</span></div>
<div class="p1">
<span style="color: blue;"><br /></span></div>
<div class="p1">
<span style="color: blue;">(defn add-todo [title]</span></div>
<div class="p1">
<span style="color: blue;"> (insert items</span></div>
<div class="p1">
<span style="color: blue;"> (values {:title title})))</span></div>
<div class="p1">
<span style="color: blue;"><br /></span></div>
<div class="p1">
<span style="color: blue;">(defn delete-todo [id]</span></div>
<div class="p1">
<span style="color: blue;"> (delete items</span></div>
<div class="p1">
<span style="color: blue;"> (where {:id [= id]})))</span></div>
<div class="p1">
<span style="color: blue;"><br /></span></div>
<div class="p1">
<span style="color: blue;">(defn update-todo [id title is-complete]</span></div>
<div class="p1">
<span style="color: blue;"> (update items</span></div>
<div class="p1">
<span style="color: blue;"> (set-fields {:title title</span></div>
<div class="p1">
<span style="color: blue;"> :is_complete is-complete})</span></div>
<div class="p1">
<span style="color: blue;"> (where {:id [= id]})))</span></div>
<div class="p1">
<span style="color: blue;"><br /></span></div>
<div class="p1">
<span style="color: blue;">(defn get-todo [id]</span></div>
<div class="p1">
<span style="color: blue;"> (first</span></div>
<div class="p1">
<span style="color: blue;"> (select items</span></div>
<div class="p1">
<span style="color: blue;"> (where {:id [= id]}))))</span><br />
<span style="color: blue;"><br /></span>
<span style="color: blue;"><i>Update 2019:</i></span><br />
<span style="color: blue;"><br /></span>
<span style="color: blue;"></span><br />
<pre style="background-color: white; font-family: Menlo; font-size: 9pt;"><i>(<span style="color: navy; font-weight: bold;">ns </span>asado.query
(<span style="color: #660e7a;">:require </span>[asado.database <span style="color: #660e7a;">:refer </span>[db]]
[korma.core <span style="color: #660e7a;">:refer :all</span>]
[schema.core <span style="color: #660e7a;">:as </span>s]))
(<span style="color: navy; font-weight: bold;">s/defschema </span>TitleBody
{<span style="color: #660e7a;">:title </span>s/Str})
(<span style="color: navy; font-weight: bold;">s/defschema </span>TodoBody
{<span style="color: #660e7a;">:id </span>s/Int
<span style="color: #660e7a;">:title </span>s/Str
<span style="color: #660e7a;">:is_complete </span>s/Bool})
(<span style="color: navy; font-weight: bold;">defentity </span>items)
(<span style="color: navy; font-weight: bold;">defn </span>get-todos []
(<span style="color: navy; font-weight: bold;">select </span>items))
(<span style="color: navy; font-weight: bold;">defn </span>add-todo [title]
(<span style="color: navy; font-weight: bold;">insert </span>items
(<span style="color: navy; font-weight: bold;">values </span>{<span style="color: #660e7a;">:title </span>title})))
(<span style="color: navy; font-weight: bold;">defn </span>delete-todo [id]
(<span style="color: navy; font-weight: bold;">delete </span>items
(<span style="color: navy; font-weight: bold;">where </span>{<span style="color: #660e7a;">:id </span>id})))
(<span style="color: navy; font-weight: bold;">defn </span>update-todo [id title is-complete]
(<span style="color: navy; font-weight: bold;">update </span>items
(<span style="color: navy; font-weight: bold;">set-fields </span>{<span style="color: #660e7a;">:title </span>title
<span style="color: #660e7a;">:is_complete </span>is-complete})
(<span style="color: navy; font-weight: bold;">where </span>{<span style="color: #660e7a;">:id </span>id})))
(<span style="color: navy; font-weight: bold;">defn </span>get-todo [id]
(<span style="color: navy; font-weight: bold;">first</span><span style="color: navy; font-weight: bold;"> </span>(<span style="color: navy; font-weight: bold;">select </span>items
(<span style="color: navy; font-weight: bold;">where </span>{<span style="color: #660e7a;">:id </span>id}))))</i></pre>
</div>
<div class="p1">
<br /></div>
<div class="p1">
All done. SqlKorma is extremely easy to use, very composable.</div>
<div class="p1">
<br /></div>
<div class="p1">
Ok, now let's write the REST services:</div>
<div class="p1">
<br /></div>
<div class="p1">
<span style="color: blue;">(ns todoapp2.handler</span></div>
<div class="p1">
<span style="color: blue;"> (:require [compojure.core :refer :all]</span></div>
<div class="p1">
<span style="color: blue;"> <span class="Apple-tab-span" style="white-space: pre;"> </span>[compojure.handler :as handler]</span></div>
<div class="p1">
<span style="color: blue;"> [compojure.route :as route]</span></div>
<div class="p1">
<span style="color: blue;"> [ring.middleware.json :as json]</span></div>
<div class="p1">
<span style="color: blue;"> [ring.util.response :refer [response]]</span></div>
<div class="p1">
<span style="color: blue;"> [todoapp2.query :refer :all]))</span></div>
<div class="p1">
<span style="color: blue;"><br /></span></div>
<div class="p1">
<span style="color: blue;">(defroutes app-routes</span></div>
<div class="p1">
<span style="color: blue;"> (GET "/api/todos" []</span></div>
<div class="p1">
<span style="color: blue;"> (response (get-todos)))</span></div>
<div class="p1">
<span style="color: blue;"> (GET "/api/todos/:id" [id]</span></div>
<div class="p1">
<span style="color: blue;"> (response (get-todo (Integer/parseInt id))))</span></div>
<div class="p1">
<span style="color: blue;"> (POST "/api/todos" [title]</span></div>
<div class="p1">
<span style="color: blue;"> (response (add-todo title)))</span></div>
<div class="p1">
<span style="color: blue;"> (PUT "/api/todos/:id" [id title is_complete]</span></div>
<div class="p1">
<span style="color: blue;"> (response (update-todo (Integer/parseInt id) title is_complete)))</span></div>
<div class="p1">
<span style="color: blue;"> (DELETE "/api/todos/:id" [id]</span></div>
<div class="p1">
<span style="color: blue;"> (response (delete-todo (Integer/parseInt id))))</span></div>
<div class="p1">
<span style="color: blue;"> (route/resources "/")</span></div>
<div class="p1">
<span style="color: blue;"> (route/not-found "Not Found"))</span></div>
<div class="p1">
<span style="color: blue;"><br /></span></div>
<div class="p1">
<span style="color: blue;">(def app</span></div>
<div class="p1">
<span style="color: blue;"> (-> (handler/api app-routes)</span></div>
<div class="p1">
<span style="color: blue;"> (json/wrap-json-params)</span></div>
<div class="p1">
<span style="color: blue;"> (json/wrap-json-response)))</span><br />
<span style="color: blue;"><br /></span>
<span style="color: blue;">Update 2019, using compojure-api:</span></div>
<div class="p1">
<br />
<pre style="background-color: white; font-family: Menlo; font-size: 9pt;">(<span style="color: navy; font-weight: bold;">ns </span>asado.handler
(<span style="color: #660e7a; font-style: italic;">:require </span>[compojure.api.sweet <span style="color: #660e7a; font-style: italic;">:refer :all</span>]
[ring.util.http-response <span style="color: #660e7a; font-style: italic;">:refer :all</span>]
[schema.core <span style="color: #660e7a; font-style: italic;">:as </span>s]
[asado.query <span style="color: #660e7a; font-style: italic;">:as </span>q]))
(<span style="color: navy; font-weight: bold;">def </span>app
(<span style="color: navy; font-weight: bold;">api</span><span style="color: navy; font-weight: bold;"> </span>{<span style="color: #660e7a; font-style: italic;">:swagger</span><span style="color: #660e7a; font-style: italic;"> </span>{<span style="color: #660e7a; font-style: italic;">:ui </span><span style="color: green; font-weight: bold;">"/"</span><span style="color: green; font-weight: bold;"> </span><span style="color: #660e7a; font-style: italic;">:spec </span><span style="color: green; font-weight: bold;">"/swagger.json"</span><span style="color: green; font-weight: bold;"> </span><span style="color: #660e7a; font-style: italic;">:data </span>{<span style="color: #660e7a; font-style: italic;">:info </span>{<span style="color: #660e7a; font-style: italic;">:title </span><span style="color: green; font-weight: bold;">"Asado"</span><span style="color: green; font-weight: bold;"> </span><span style="color: #660e7a; font-style: italic;">:description </span><span style="color: green; font-weight: bold;">"Compojure Api example"</span>}
<span style="color: #660e7a; font-style: italic;">:tags </span>[{<span style="color: #660e7a; font-style: italic;">:name </span><span style="color: green; font-weight: bold;">"api"</span>, <span style="color: #660e7a; font-style: italic;">:description </span><span style="color: green; font-weight: bold;">"some apis"</span>}]}}}
(<span style="color: navy; font-weight: bold;">context </span><span style="color: green; font-weight: bold;">"/api" </span>[]
<span style="color: #660e7a; font-style: italic;">:tags </span>[<span style="color: green; font-weight: bold;">"api"</span>]
(<span style="color: navy; font-weight: bold;">GET </span><span style="color: green; font-weight: bold;">"/todos" </span>[]
(<span style="color: navy; font-weight: bold;">ok </span>(<span style="color: navy; font-weight: bold;">q/get-todos </span>)))
(<span style="color: navy; font-weight: bold;">GET </span><span style="color: green; font-weight: bold;">"/api/todos/:id" </span>[]
<span style="color: #660e7a; font-style: italic;">:path-params </span>[id <span style="color: #660e7a; font-style: italic;">:- </span>s/Int]
(<span style="color: navy; font-weight: bold;">ok </span>(<span style="color: navy; font-weight: bold;">q/get-todo </span>id)))
(<span style="color: navy; font-weight: bold;">POST </span><span style="color: green; font-weight: bold;">"/api/todos" </span>[]
<span style="color: #660e7a; font-style: italic;">:body </span>[title-body q/TitleBody]
(<span style="color: navy; font-weight: bold;">let </span>[{<span style="color: #660e7a; font-style: italic;">:keys </span>[title]} title-body]
(<span style="color: navy; font-weight: bold;">ok </span>(<span style="color: navy; font-weight: bold;">q/add-todo </span>title))))
(<span style="color: navy; font-weight: bold;">PUT </span><span style="color: green; font-weight: bold;">"/api/todos" </span>[]
<span style="color: #660e7a; font-style: italic;">:body </span>[todo-body q/TodoBody]
(<span style="color: navy; font-weight: bold;">let </span>[{<span style="color: #660e7a; font-style: italic;">:keys </span>[id title is_complete]} todo-body]
(<span style="color: navy; font-weight: bold;">ok </span>{<span style="color: #660e7a; font-style: italic;">:updated </span>(<span style="color: navy; font-weight: bold;">q/update-todo </span>id title is_complete)})))
(<span style="color: navy; font-weight: bold;">DELETE </span><span style="color: green; font-weight: bold;">"/api/todos/:id" </span>[]
<span style="color: #660e7a; font-style: italic;">:path-params </span>[id <span style="color: #660e7a; font-style: italic;">:- </span>s/Int]
(<span style="color: navy; font-weight: bold;">ok </span>{<span style="color: #660e7a; font-style: italic;">:deleted </span>(<span style="color: navy; font-weight: bold;">q/delete-todo </span>id)}))
)))
</pre>
<br /></div>
<div class="p1">
Starting the server:</div>
<div class="p1">
<br /></div>
<div class="p1">
<b>>lein ring start</b></div>
<div class="p1">
<br /></div>
<div class="p1">
Using a tool like Advanced REST Client plugin for Chrome will allow you to use the API:</div>
<div class="p1">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixJQeFfIzMSBSS9TuUPv225iBZw64g0Gwb03Ked62NycV-hJJvvDZSLVNVQjDY4jvIpQHn_2JuxHPJritcfrBMZqaEnFnCzImfO3Wbrk2Wk2bQcafCkekFwR6dAshr9_c-D8Ze/s1600/Screen+Shot+2015-10-17+at+19.51.37.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="275" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixJQeFfIzMSBSS9TuUPv225iBZw64g0Gwb03Ked62NycV-hJJvvDZSLVNVQjDY4jvIpQHn_2JuxHPJritcfrBMZqaEnFnCzImfO3Wbrk2Wk2bQcafCkekFwR6dAshr9_c-D8Ze/s640/Screen+Shot+2015-10-17+at+19.51.37.png" width="640" /></a></div>
<div class="p1">
<br /></div>
<div class="p1">
<br /></div>
<div class="p1">
<br /></div>
<div class="p1">
And accessing http://localhost:3000/api/todos will show you what you created.<br />
<br />
Update 2019<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhg5uE2lzhW5IFP4cI-Rp0bcW74NBCYef_zc4ilrUEMnyNdl4jUI-49T7eAlBfK1fm-pAqi0w1VborFojywgedbAMcTMmjuN3R-BU45DSS3f2nmHmed9g5c8sg3m6qzdZ34uIIL/s1600/Screenshot+2019-10-16+at+09.24.20.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1115" data-original-width="1600" height="443" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhg5uE2lzhW5IFP4cI-Rp0bcW74NBCYef_zc4ilrUEMnyNdl4jUI-49T7eAlBfK1fm-pAqi0w1VborFojywgedbAMcTMmjuN3R-BU45DSS3f2nmHmed9g5c8sg3m6qzdZ34uIIL/s640/Screenshot+2019-10-16+at+09.24.20.png" width="640" /></a></div>
<br /></div>
<div class="p1">
<br /></div>
<h4>
Conclusion</h4>
<div class="p1">
<br /></div>
<div class="p1">
Pretty awesome!</div>
</div>
<div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com1tag:blogger.com,1999:blog-9648422.post-88266121878831666582015-02-06T12:15:00.001+02:002019-09-16T11:37:16.591+03:00Fighting complexity through functional composition, part 1: how to implement functional composition<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<h2>
</h2>
<div id="mb-reply" style="font-size: 14px; font-weight: normal;">
<div>
<br /></div>
</div>
<div>
The javascript code online:<br />
<a href="http://jsfiddle.net/danbunea1/gz87dt5a/">http://jsfiddle.net/danbunea1/gz87dt5a/</a><br />
<br /></div>
<h2>
Complexity</h2>
</div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<div>
<br /></div>
<div>
The absolute enemy in software (and other things as well) is complexity. Considering complexity as the opposite of simple, it makes our systems hard to understand, hard to debug and hard to extend or adapt. There is one very good talk of the great Rich Hickey, about this called: Simplicity Matters. The video: <a href="https://www.youtube.com/watch?v=rI8tNMsozo0">https://www.youtube.com/watch?v=rI8tNMsozo0</a>. So let;s see how we can fight complexity in a practical example by using functional composition</div>
<h3>
</h3>
<h3>
The problem </h3>
</div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<div>
<br /></div>
</div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<br /></div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<div id="mb-reply">
Considering that these days most of the integration is done through web services and JSON, we'll try to illustrate the complexity problem using an example from this area.</div>
</div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<br />
Requirement: in the json, we need to have a key “measurement”, that is mandatory, cannot be null, needs to be a string and cannot be empty string.</div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<br /></div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
We’ll do a little bit of TDD here, starting with a test:</div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<br /></div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<div>
<div>
Python:
<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> class TestValidations(unittest.TestCase):
def validate_pair(self,json, is_valid, number_of_errors):
errors = validate_simplest_json(json)
print "",is_valid, errors, json
self.assertEquals(len(errors)==0,is_valid)
self.assertEquals(len(errors),number_of_errors)
def test_json_validation(self):
self.validate_pair({},False,1)
def validate_simplest_json(json):
errors = []
return errors
</code></pre>
<br />
<br />
<!--br />Javascript:
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> function validate_pair(json, is_valid, number_of_errors){
var errors = validate_simplest_json(json)
console.log("",is_valid, errors, json);
QUnit.equal(errors.length==0,is_valid);
QUnit.equal(errors.length,number_of_errors);
}
test("simple json imperative", function () {
validate_pair({},false,1);
});
function validate_simplest_json(json){
var errors = [];
return errors;
}
</code></pre-->
</div>
</div>
</div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<br /></div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
All fail, which is great. Now let’s write the code, to check if the key is there:</div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<br /></div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<div>
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> def validate_simplest_json(json):
errors = []
if not json.has_key("measurement"):
errors.append("measurement cannot be missing”)
return errors
</code></pre>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
Pass. Now what about null? The test extends:</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
<div>
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> def test_json_validation(self):
self.validate_pair({},False,1)
self.validate_pair({"measurement":None},False,1)
</code></pre>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
the code to pass:</div>
</div>
</div>
</div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<br /></div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<div>
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> def validate_simplest_json(json):
errors = []
if not json.has_key("measurement"):
errors.append("measurement cannot be missing")
else:
if json["measurement"]==None:
errors.append("measurement cannot be null”)
return errors
</code></pre>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
Pass. Now let’s check if it is a string (or unicode):</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
<div>
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> def test_json_validation(self):
self.validate_pair({},False,1)
self.validate_pair({"measurement":None},False,1)
self.validate_pair({"measurement":-1},False,1)
self.validate_pair({"measurement":{}},False,1)
self.validate_pair({"measurement":False},False,1)
self.validate_pair({"measurement":"abc"},True,0)
self.validate_pair({"measurement":u"Citroën"},True,0)
def validate_simplest_json(json):
errors = []
if not json.has_key("measurement"):
errors.append("measurement cannot be missing")
else:
if json["measurement"]==None:
errors.append("measurement cannot be null")
else:
if not isinstance(json["measurement"], str) and not isinstance(json["measurement"], unicode):
errors.append("measurement needs to string or unicode")
return errors
</code></pre>
</div>
</div>
<div id="mb-reply">
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
Now we also need to check if it is not emty string:</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> def test_json_validation(self):
self.validate_pair({},False,1)
self.validate_pair({"measurement":None},False,1)
self.validate_pair({"measurement":-1},False,1)
self.validate_pair({"measurement":{}},False,1)
self.validate_pair({"measurement":False},False,1)
self.validate_pair({"measurement":"abc"},True,0)
self.validate_pair({"measurement":u"Citroën"},True,0)
self.validate_pair({"measurement":""},False,1)
def validate_simplest_json(json):
errors = []
if not json.has_key("measurement"):
errors.append("measurement cannot be missing")
else:
if json["measurement"]==None:
errors.append("measurement cannot be null")
else:
if not isinstance(json["measurement"], str) and not isinstance(json["measurement"], unicode):
errors.append("measurement needs to string or unicode")
else:
if len(json["measurement"].strip())==0:
errors.append("measurement cannot be an empty string")
return errors
</code></pre>
</div>
</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
All of the sudden, we hear we also need to make sure the length of the string is between 3 and 8 characters, and cannot be some reserved words like “password” or “archived"</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
Ok, so let’s code, expanding our tests:</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> def test_json_validation(self):
self.validate_pair({},False,1)
self.validate_pair({"measurement":None},False,1)
self.validate_pair({"measurement":-1},False,1)
self.validate_pair({"measurement":{}},False,1)
self.validate_pair({"measurement":False},False,1)
self.validate_pair({"measurement":"abc"},True,0)
self.validate_pair({"measurement":u"Citroën"},True,0)
self.validate_pair({"measurement":""},False,1)
self.validate_pair({"measurement":"a"},False,1)
self.validate_pair({"measurement":"abcdefghijklmnefghij"},False,1)
self.validate_pair({"measurement":"password"},False,1)
self.validate_pair({"measurement":"archived"},False,1)
self.validate_pair({"measurement":"arCHived"},False,1)
</code></pre>
</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
Then gradually we start coding the validation, arriving to:</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
<div>
<div>
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> def validate_simplest_json(json):
errors = []
if not json.has_key("measurement"):
errors.append("measurement cannot be missing")
else:
if json["measurement"]==None:
errors.append("measurement cannot be null")
else:
if not isinstance(json["measurement"], str) and not isinstance(json["measurement"], unicode):
errors.append("measurement needs to string or unicode")
else:
lenm=len(json["measurement"].strip())
if lenm==0:
errors.append("measurement cannot be an empty string")
else:
if lenm<3: data-blogger-escaped-div="">
errors.append("measurement needs at least 3 characters")
elif lenm&gt;10:
errors.append("measurement needs at most 10 characters")
elif json["measurement"].strip().lower() in ["archived","password"]:
errors.append("measurement has a value which is not allowed")
return errors
</code></pre>
</div>
<div>
<!--3:--></div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
As requirements are added complexity grows. Now of course this code could be refactored, but eliminating the essential problem of complexity is very hard. Just imagine what will happen if at version 1.2 the customer will change the API and only allow the values to be a measurement unit like “0.12mm” or “13.2mg”. It will grow again and become more complex. Not pretty!</div>
</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
And having json with only one key is kind of rare… Usually the number of keys is a lot higher and of course the code a lot bigger. Bigger and more complex = disaster. In terms of code quality it will fail at being able to extend it easily and it will fail at being able to debug it easily.<br />
<br />
<br /></div>
</div>
</div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<h3>
The solution: implementing functional composition</h3>
</div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<br />
<br /></div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
Removing complexity can mean, more linear code, so let’s refactor it to be more linear:</div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<br /></div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<div>
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> def validate_simplest_json_imperative_linear(json):
errors = []
should_exit=False
key = "measurement"
if not key_exists(json,key):
errors.append("{0} cannot be missing".format(key))
should_exit=True
if not should_exit:
if value_null(json, key):
errors.append("{0} cannot be null".format(key))
should_exit=True
if not should_exit:
if not is_string_or_unicode(json, key):
errors.append("{0} needs to string or unicode".format(key))
should_exit=True
if not should_exit:
if is_empty_string(json, key):
errors.append("{0} cannot be an empty string".format(key))
should_exit=True
if not should_exit:
lenm=len(json[key].strip())
if lenm<3: data-blogger-escaped-div="">
errors.append("{0} needs at least 3 characters".format(key))
should_exit=True
elif lenm&gt;10:
errors.append("{0} needs at most 10 characters".format(key))
should_exit=True
if not should_exit:
if json[key].strip().lower() in ["archived","password"]:
errors.append("{0} has a value which is not allowed".format(key))
should_exit=True
return errors
</code></pre>
</div>
<div>
<!--3:--></div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<br /></div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
And yes, all the tests still pass. But we’re far from over, although we do see a pattern by which each method is executed after the other… Hmm, now I’ll move all variables like json, key, errors and exit into a single object (a tuple) so that we don’t pass 4 parameters back and forth:</div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<br /></div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<div id="mb-reply">
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> ValidationState = namedtuple("ValidationState","json key errors exit”)
</code></pre>
</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
then I will extract the actual validations in simple functions, like:</div>
<div id="mb-reply">
<div>
<br /></div>
<div>
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> def validate_simplest_json_imperative_linear_with_state(json):
initial_state = ValidationState(json=json, key="measurement",errors=[], exit=False)
state = validate_key_exists(initial_state)
if not state.exit:
state = validate_not_null(state)
if not state.exit:
state = validate_string_or_unicode(state)
if not state.exit:
state = validate_not_empty_string(state)
if not state.exit:
state = validate_length(state, 3,10)
if not state.exit:
state = validate_not_in(state, ["archived","password"])
return state.errors
</code></pre>
</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
And the functions:</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
<div>
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> def validate_key_exists(state):
print validate_key_exists.__name__,state
if not key_exists(state.json,state.key):
return state._replace(errors = state.errors+["{0} cannot be missing".format(state.key)])._replace(exit=True)
return state
def validate_not_null(state):
print validate_not_null.__name__,state
if value_null(state.json,state.key):
return state._replace(errors = state.errors+["{0} cannot be null".format(state.key)])._replace(exit=True)
return state
def validate_string_or_unicode(state):
print validate_string_or_unicode.__name__,state
if not is_string_or_unicode(state.json,state.key):
return state._replace(errors = state.errors+["{0} needs to string or unicode".format(state.key)])._replace(exit=True)
return state
def validate_not_empty_string(state):
print validate_not_empty_string.__name__,state
if is_empty_string(state.json,state.key):
return state._replace(errors = state.errors+["{0} cannot be an empty string".format(state.key)])._replace(exit=True)
return state
def validate_length(state, min, max):
print validate_length.__name__,state
lenm=len(state.json[state.key].strip())
if lenm
return state._replace(errors = state.errors+["{0} needs at least 3 characters".format(state.key)])._replace(exit=True)
elif lenm&gt;max:
return state._replace(errors = state.errors+["{0} needs at most 10 characters".format(state.key)])._replace(exit=True)
return state
def validate_not_in(state,vals):
print validate_not_in.__name__,state
if state.json[state.key].strip().lower() in vals:
return state._replace(errors = state.errors+["{0} has a value which is not allowed".format(state.key)])._replace(exit=True)
return state
</code></pre>
</div>
<div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
The code looks is now a series of functions that run with the result of the previous function if the exit parameter is not set to True. So basically having 2 functions f,g they’ll be composed like:</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
<i><b>initial_state = …</b></i></div>
<div id="mb-reply">
<i><b>state = f(initial_state)</b></i></div>
<div id="mb-reply">
<i><b>if not state.exit:</b></i></div>
<div id="mb-reply">
<i><b> return g(state)</b></i></div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
And putting this in a function:</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
<div>
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> def compose2(f, g):
def run(x):
result_f = f(x)
if not result_f.exit:
return g(result_f)
else:
return result_f
return run
</code></pre>
</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
Using this we can now compose 2 functions into one:</div>
</div>
<div id="mb-reply">
<div>
<br /></div>
<div>
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> def validate_simplest_json_imperative_linear_with_state(json):
initial_state = ValidationState(json=json, key="measurement",errors=[], exit=False)
# state = validate_key_exists(initial_state)
#
# if not state.exit:
# state = validate_not_null(state)
composed_function = compose2(validate_key_exists, validate_not_null)
state = composed_function(initial_state)
</code></pre>
</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
But we don’t have only 2 function, we have more, so we write a reduce:</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
<div>
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> #compose n functions
def compose(*functions):
return reduce(compose2, functions)
</code></pre>
</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
and out function becomes:</div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> def validate_simplest_json_imperative_linear_with_state(json):
initial_state = ValidationState(json=json, key="measurement",errors=[], exit=False)
composed_function = compose(validate_key_exists, validate_not_null,validate_string_or_unicode,validate_not_empty_string)
state = composed_function(initial_state)
if not state.exit:
state = validate_length(state, 3,10)
if not state.exit:
state = validate_not_in(state, ["archived","password"])
return state.errors
</code></pre>
</div>
</div>
</div>
<div id="mb-reply">
<br /></div>
</div>
</div>
</div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
but we just hit a problem. He have some functions that have more parameters and we need to pass them. We’ll use closures:</div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<br /></div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<div>
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> def create_validate_length(min, max):
def validate_length(state):
print validate_length.__name__,state
lenm=len(state.json[state.key].strip())
if lenm
return state._replace(errors = state.errors+["{0} needs at least 3 characters".format(state.key)])._replace(exit=True)
elif lenm&gt;max:
return state._replace(errors = state.errors+["{0} needs at most 10 characters".format(state.key)])._replace(exit=True)
return state
return validate_length
def create_validate_not_in(vals):
def validate_not_in(state):
print validate_not_in.__name__,state
if state.json[state.key].strip().lower() in vals:
return state._replace(errors = state.errors+["{0} has a value which is not allowed".format(state.key)])._replace(exit=True)
return state
return validate_not_in
</code></pre>
</div>
<div>
<div id="mb-reply">
<br /></div>
<div id="mb-reply">
And now the final validation code:</div>
<div id="mb-reply">
<div>
<br /></div>
<div>
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> def validate_simplest_functional_composition(json):
initial_state = ValidationState(json=json, key="measurement",errors=[], exit=False)
composed_function = compose(validate_key_exists, validate_not_null,validate_string_or_unicode,validate_not_empty_string, create_validate_length(3, 10), create_validate_not_in(["archived","password"]))
final_state = composed_function(initial_state)
return final_state.errors
</code></pre>
</div>
</div>
</div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<br /></div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
It is much better. It basically says: having an initial start of the system, run all these functions (validators) and at the end get a final state.<br />
<br />
<div id="mb-reply">
Now the entire code is available online and can also be run at:<br />
<br />
<a href="http://runnable.com/VNMhoTKLSn9Tm0GI/fighting-complexity-through-functional-composition-for-python">http://runnable.com/VNMhoTKLSn9Tm0GI/fighting-complexity-through-functional-composition-for-python</a></div>
<div id="mb-reply">
<br />
a preview:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://runnable.com/VNMhoTKLSn9Tm0GI/fighting-complexity-through-functional-composition-for-python" target="_blank"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjg0F9C66UrmyzqsH2eUJnUIgv-IxRTbWgu27r7joeCzdgvkfDD02XVOOZCQyadhrQnURDk157Naydqd7HoSXqDTOeH4d5v7IPG04wBtxEhLXqoqOJsb__uh_Av1jGUMBdhwQS/s1600/Screen+Shot+2015-02-06+at+11.24.03.png" width="456" /></a></div>
<br />
Or in Javascript: <a href="http://jsfiddle.net/danbunea1/gz87dt5a/">http://jsfiddle.net/danbunea1/gz87dt5a/</a><br />
<a href="http://jsfiddle.net/danbunea1/gz87dt5a/" target="_blank"><br /></a>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://jsfiddle.net/danbunea1/gz87dt5a/" target="_blank"><img border="0" height="542" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNjf-QCwwIzXLwUpuTKla8TriQF7MkTp999LR57AACn6Ej5gn3dvJd3iJYjF4amJBcILWQ6luM1X4I5jXVfG1f-wQhoKUdRtl8RPpwXaifXCWSFKIiAkNBt9Jr7_JJ2Ip9f4tl/s1600/Screen+Shot+2015-02-06+at+16.43.19.png" width="640" /></a></div>
<a href="http://jsfiddle.net/danbunea1/gz87dt5a/" target="_blank"><br /></a></div>
</div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<br />
<br /></div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<b>Conclusion: Why is this better?</b></div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<br />
<br />
<br />
Now you would think, how can a solution with ... lines of code be better then one with just 21. In part 2 of the article, called "Why is functional composition better" I will illustrate why, and how functional composition makes our code simpler, easier to understand, debug and change.<br />
<br /></div>
<div id="mb-reply" style="color: #333333; font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; font-size: 14px; line-height: 20px;">
<br /></div>
</div>
</div>
</div>
</div>
</div>
<div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-12898870669399292022015-02-01T12:16:00.000+02:002015-02-01T12:16:21.014+02:00The value of values, by Rich Hickey - prerequisite for functional programmingIf you're thinking of trying out functional programming, there are many things you need to understand, but the most important would be this:<br />
<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="270" src="https://www.youtube.com/embed/-6BsiVyC1kM" width="480"></iframe><div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-47644130622601256122014-12-19T15:58:00.002+02:002014-12-19T15:58:31.903+02:00Functional programming (in javascript and python): Part 1: Why?<b>The problem</b><br />
<br />
At the beginning of this year I started thinking how would I describe what I did as a programmer for the last 13 years in just a few words, and this is the conclusion:<br />
<br />
<i>List processing and debugging. </i><br />
<br />
Now let me explain: If I abstract enough, everything looks like a list: arrays, dictionaries even objects, considering them a list of properties with values. Complex objects become lists of lists. They're everywhere: from databases where these days you get back lists of rows/objects, to models, to the UI for instance $("...") in jQuery. So basically all day long I wrote functions and methods to process these lists and lists of lists, transforming them, changing them, persisting and loading them, send and receive them through the networks and internet or displaying them on the screen.<br />
<br />
The big problem is that if the code becomes a little complex, you expect at the end of some processing some result and it's not there. Basically you expect the system to be in a state, and it is in another, thus having an error or a bug, so you start following what happens to see why. If the system uses events and async operations your debugging work starts to take a lot of time and effort. Thinking about time, I would correct the conclusion above to<br />
<br />
<i>30% list processing, 70% debugging</i><br />
<br />
<b>The causes</b><br />
<br />
For years I looked for solutions: from OOP, to patterns, to separation of concerns, agile methodologies practices such as unit testing and test driven development. All helped, but none seem to address the biggest issues: state is everywhere thus unexpected state changes, not to mention concurrent programming.<br />
<br />
<b>The solution</b><br />
<br />
Then I remembered about LISP (List programming), and how strange it seemed. But somehow it seems to do exactly what I needed, unfortunately not in the programming languages that I needed, but then I started to realise that the languages I worked with are multi paradigm and yes functional programming concepts are embedded right in (javascript/python).<br />
<br />
So basically what functional programming addresses is: list processing using 3 main functions: map, reduce and filter. Using these 3 functions, simpler data structures like arrays, dictionaries and lists instead of objects used as data structures and higher order functions, the code to process lists becomes simpler and smaller.<br />
<br />
<b>Example</b><br />
<br />
Let's see the total of an order in the shopping cart, for groceries:<br />
<br />
Python, using imperative programming and OOP (badly, like pretty much everyone):<br />
<br />
<span style="color: blue;">class Order()</span><br />
<span style="color: blue;"> orderItems = []</span><br />
<span style="color: blue;"> def __init__():</span><br />
<span style="color: blue;"> pass</span><br />
<span style="color: blue;">...</span><br />
<span style="color: blue;"><br /></span>
<span style="color: blue;">class OrderItem()</span><br />
<span style="color: blue;"> name = None</span><br />
<span style="color: blue;"> quantity = 0.0</span><br />
<span style="color: blue;"> price = 0.0</span><br />
<span style="color: blue;"><br /></span>
<span style="color: blue;"> def __init__(name=None, quantity=0.0, price=0.0):</span><br />
<span style="color: blue;"> self.name=name</span><br />
<div>
<span style="color: blue;"> self.quantity =quantity</span></div>
<div>
<span style="color: blue;"> self.price=price</span></div>
<div>
<span style="color: blue;">...</span></div>
<span style="color: blue;"><br /></span>
<span style="color: blue;">order = Order()</span><br />
<span style="color: blue;">order.orderItems.append(OrderItem(name="", quantity=1,price=0.99))</span><br />
<div>
<span style="color: blue;">order.orderItems.append(OrderItem(name="", quantity=1,price=0.99))</span></div>
<div>
<span style="color: blue;"><br /></span></div>
<span style="color: blue;">def is_grocery(item)</span><br />
<span style="color: blue;"> return ...</span><br />
<span style="color: blue;"><br /></span>
<span style="color: blue;">sum = 0</span><br />
<span style="color: blue;">for item in order.orderItems</span><br />
<span style="color: blue;"> if is_grocery(item):</span><br />
<span style="color: blue;"> sum+=item.quantity*item.price</span><br />
<span style="color: blue;"><br /></span>
<span style="color: blue;">print sum</span><br />
<br />
Python, using FP:<br />
<br />
<span style="color: blue;">order = {"orderItems":[</span><br />
<span style="color: blue;"> { "name":"sada""quantity":2,"price":0.99},</span><br />
<span style="color: blue;"> {"name":"zjdfj""quantity":1,"price":2.99},</span><br />
<div>
<span style="color: blue;"> ...]}</span></div>
<span style="color: blue;"><br /></span>
<span style="color: blue;">def is_grocery(item):</span><br />
<span style="color: blue;"> return ...</span><br />
<span style="color: blue;"><br /></span>
<span style="color: blue;">print reduce(</span><br />
<span style="color: blue;"> lambda total1,total2: total1+total2,</span><br />
<span style="color: blue;"> map(</span><br />
<span style="color: blue;"> lambda orderItem:</span><span style="color: blue;"> </span><span style="color: blue;">i1["quantity"]*i1["price"]</span><br />
<span style="color: blue;"> filter(</span><br />
<span style="color: blue;"> lambda orderItem:is_grocery(orderItem),</span><br />
<span style="color: blue;"> order["orderItems"]</span><br />
<span style="color: blue;"> )</span><br />
<span style="color: blue;"> )</span><br />
<br />
About state changes, we'll speak in the next article.<br />
<br />
<b>Conclusion</b><br />
<br />
So why didn't functional programming replace imperative programming since it has many areas where it clearly is better (maybe not in the example above)? Because it is much more difficult to understand and use, even though the resulting code is smaller. If you look at the code above you see that is starts the other way round: it wants to "reduce" 2 totals for order items and then you see that the reduce is applied on a list of totals for order items which is applied on a filtered list of items that are groceries. This is the reason people say functional programming shows what the code does not how.<br />
<br />
<br />
<br />
<br /><div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-72879187753584705122014-07-31T13:36:00.005+03:002014-07-31T13:36:41.400+03:00Hierarchical persistent data structures in javascript (using underscore.js)Lately, I have been studying a lot about functional programming, and things like LISP, Clojure, Scala but since mostly I program in Javascript and python, I also studied a lot the functional programming concepts in these 2 languages.<br />
<br />
<h4>
The problem</h4>
I have a model (using MVC pattern) and I want to keep both the initial model state, and the one that is changed afar some operations. I wanted initially to use something like cloning or deep copying but it seems just to waste memory, if for instance I change only one thing in a hierarchy, so I don't need everything clones.<br />
<h4>
The solution : persistent data structures</h4>
I knew that what I wanted is to use persistent data structures in javascript and although they are available in libraries such as <a href="https://www.npmjs.org/package/immutable" target="_blank">immutable</a> or <a href="http://swannodette.github.io/mori/" target="_blank">mori</a>, I needed one that is hierarchical.<br />
<br />
Persistent data sturctures acording to wikipedia:<br />
<br />
<i><span style="background-color: white; color: #252525; font-family: sans-serif; font-size: 14px; line-height: 22px;">In </span><a href="http://en.wikipedia.org/wiki/Computing" style="background-color: white; background-image: none; color: #0b0080; font-family: sans-serif; font-size: 14px; line-height: 22px; text-decoration: none;" title="Computing">computing</a><span style="background-color: white; color: #252525; font-family: sans-serif; font-size: 14px; line-height: 22px;">, a </span><b style="background-color: white; color: #252525; font-family: sans-serif; font-size: 14px; line-height: 22px;">persistent data structure</b><span style="background-color: white; color: #252525; font-family: sans-serif; font-size: 14px; line-height: 22px;"> is a </span><a href="http://en.wikipedia.org/wiki/Data_structure" style="background-color: white; background-image: none; color: #0b0080; font-family: sans-serif; font-size: 14px; line-height: 22px; text-decoration: none;" title="Data structure">data structure</a><span style="background-color: white; color: #252525; font-family: sans-serif; font-size: 14px; line-height: 22px;"> that always preserves the previous version of itself when it is modified. Such data structures are effectively </span><a href="http://en.wikipedia.org/wiki/Immutable_object" style="background-color: white; background-image: none; color: #0b0080; font-family: sans-serif; font-size: 14px; line-height: 22px; text-decoration: none;" title="Immutable object">immutable</a><span style="background-color: white; color: #252525; font-family: sans-serif; font-size: 14px; line-height: 22px;">, as their operations do not (visibly) update the structure in-place, but instead always yield a new updated structure. (A persistent data structure is </span><span style="background-color: white; color: #252525; font-family: sans-serif; font-size: 14px; line-height: 22px;">not</span><span style="background-color: white; color: #252525; font-family: sans-serif; font-size: 14px; line-height: 22px;"> a data structure committed to </span><a class="mw-redirect" href="http://en.wikipedia.org/wiki/Persistent_storage" style="background-color: white; background-image: none; color: #0b0080; font-family: sans-serif; font-size: 14px; line-height: 22px; text-decoration: none;" title="Persistent storage">persistent storage</a><span style="background-color: white; color: #252525; font-family: sans-serif; font-size: 14px; line-height: 22px;">, such as a disk; this is a different and unrelated sense of the word "persistent.")</span></i><br />
<br />
<h4>
Solution:</h4>
<br />
So I want something like:<br />
<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> initial_state = {a:1, b:2};
modified_state = change(model,"a","abc"))
</code></pre>
<br />
where initial_state remains unchanged and modified_state is {a:"abc",b:2}. This is very simple using something like _.clone(initial_state) in underscore.js. But what if I want something like:<br />
<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> initial_state = model = {l:[{id:1}, {id:2}], b:2};
modified_state = change(model,"l[1].id",3)
</code></pre>
<br />
resulting in: modified_state = {l:{id:1},{id:3}], b:2}. Here copy, deep copy or clone doesn't help. So what if I write a function like this:<br />
<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> function change(obj,path,val)
{
var new_obj = _.clone(obj);
var path = path.replace("[",".").replace("]","");
var levels = path.split(".");
if(levels.length>1)
{
var head = _.first(levels)
var tail = path.replace(head+".","")
new_obj[head] = change(obj[head],tail,val)
}
else
{
new_obj[path] = val;
}
return new_obj;
}
</code></pre>
<br />
It works!<br />
<br />
I can even do:<br />
<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PlbZ9v116s6aW02hM1wVtjfEBcN5pGnGiCSGmhbwv3VaUD6eEDVVHNfsWh6kyq_Zwef9KtAQ5ZZZAKw_1YrrYAwo9m3RbcpERf_uPKTLtZQqJlpsN6GlTsj-SwUuRuRvu79aYA/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> initial_state = {g:
[
{id:1, values:[1,2,3]},
{id:2, values:[3,4]}
],
b:2}
new_state1 = change(model,"g[1].values[1]",7);
new_state2=change(model,"g[1].values",[7,2]);
</code></pre>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br /><div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-60440098049859990502013-07-10T14:15:00.001+03:002013-07-10T14:15:09.427+03:00We were right: 3D is the future of UI/UX as iOS7 proves itWhen we developed TaiaApps iPad app (<a href="http://www.taia76.com/">www.taia76.com</a>) we wanted to explore the possibility of building the UI, in a manner very little explored, but with massive potential for the future: 3D user interfaces. Apparently we were right guessing, what the next trend will be, as seen by the many 3D effects the new iOS7 has (parallax, movement of the folders, the new applications screen):<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.youtube.com/embed/bhSXtyziLY0?feature=player_embedded' frameborder='0'></iframe></div>
<br /><div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-36284785940491402332013-03-28T14:42:00.002+02:002013-03-28T14:42:52.594+02:0010 greatest technologies of the last 13 years in my programming careerI was thinking today about the greatest programming languages, frameworks and technologies that really had an impact in my life and career as a programmer.<br />
<br />
I will start from the beginning:<br />
<br />
1. <b>Java</b> (In 2000 I had the choice of using C++ or Java, and I thought Java was incredible. In the meanwhile it might have come and gone)<br />
<br />
2. <b>JSP and Expression Language EL</b> - Web programming with Servlets wasn't exactly easy<br />
<br />
3. <b>Hibernate</b> - the ORM that showed it is possible<br />
<br />
4. <b>Ruby On Rails</b> - the MVC framework that simplified and let to better organised web application code forever<br />
<br />
5. <b>C# and .NET - The Monorail/Activerecord/Castle frameworks - </b>like Java, they came and now they're obsolete, at least for me. But in 2005, boy they were great!<br />
<br />
6. <b>Apache Lucene</b> - searching for words in text would never be the same as before.<br />
<br />
7.<b> Javascript and jQuery</b> - when I realised how powerful Javascript can be, I started to really doubt the power of static languages<br />
<br />
8. <b>HTML5/CSS3</b> - web would never be the same after this (CSS transforms and HTML5 canvas will change the world forever). And combined with PhoneGap led to TaiaApps out first iPad app, with a 3D UI ( <a href="http://www.taia76.com/">www.taia76.com</a> )<br />
<br />
9. <b>NoSQL databases - CouchDB</b> - thinking about databases and how they keep data is now changed, as I said before - forever. Guess what powers TaiaApps?<br />
<br />
10. <b>Python - Flask/SQLAlchemy</b> - no doubt Python is a great language, but SQLAlchemy is by far the greatest ORM I have ever used.<br />
<br />
<br /><div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-89093413648477237332012-12-29T14:00:00.001+02:002012-12-29T14:00:03.537+02:00TaiaApps 3D: discover cool apps recommended by your friends - concept<iframe allowfullscreen="" frameborder="0" height="270" src="http://www.youtube.com/embed/jpTqBTNSAZ8" width="480"></iframe><div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-80660054016690770192012-12-21T12:21:00.003+02:002012-12-21T12:39:08.262+02:003D user interfaces: Navigating in space between information nodesEach information node is displayed in the middle and the related ones are grouped around it, by relation type. Tapping another information node, takes you to it, and it brings it's own related content into the scene.<br />
<br />
In our cases the information nodes can either be iPad apps or people that recommend them.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.youtube.com/embed/wp4pRo-CBJE?feature=player_embedded' frameborder='0'></iframe></div>
<br />
<br />
<br />
<br />
<br />
<a href="http://www.taia76.com/">www.taia76.com</a><div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-76880950412119330332012-12-20T11:13:00.000+02:002012-12-21T12:39:22.352+02:003D user interfaces: lists and scrolling in 3D In a 3D UI traditional vertical, or even touch horizontal scroll doesn't work anymore. Instead, the information is divided in pages, and they are stacked one in from of the other. When the user swipes the page it moves to become the last in the stack, and the one behind it becomes first. Swiping in the other direction reverses the action.<br />
<br />
<br />
<br />
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.youtube.com/embed/z1bYqGcdLAk?feature=player_embedded' frameborder='0'></iframe></div>
<br />
More on the app website: <br />
<a href="http://www.taia76.com/">www.taia76.com</a><div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-65942839303359003482012-12-16T09:41:00.003+02:002012-12-20T11:09:23.333+02:003D user interfaces in HTML5/JavaScriptI have always thought, that the next step in UI design, is adding the 3rd dimension. Throughout the history, there have been many attempts, but all of them failed because instead of simplifying the user experience they just made it harder.<br />
<br />
Last night, I and a friend released our first app, for iPad, that is our approach on 3d user interfaces.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.youtube.com/embed/IhqGsb36-iQ?feature=player_embedded' frameborder='0'></iframe>
</div>
<br />
There have been many challenges, especially since the entire UI is HTML5/Javascript based. But one by one we managed to overcome them. How would you think scrolling in 3d works? :)<br />
<br />
More information on the website: <a href="http://www.taia76.com/">www.taia76.com</a><div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-61679884064642847662012-09-17T16:39:00.002+03:002012-09-17T16:47:55.975+03:00MVC in javascriptSee code, running application and qunit test here: <a href="http://www.danbunea.ro/blogspot/mvcjs/" target="_blank">http://www.danbunea.ro/blogspot/mvcjs/</a><b><a href="http://www.danbunea.ro/blogspot/mvcjs/" target="_blank"> </a></b><br />
<br />
<b>Problem</b><br />
<br />
Today, in modern web applications, more and more code is moved from being generated server side (in java, c# with asp.net , ruby on ror, php) to be executed on the client browser, in javascript. This created a massive problem for all those used to having code well organized in modern web frameworks using the very powerfull Model View Controller pattern, since most of the code is written now in javascript.<br />
<br />
<b>Could MVC in javascript be a solution?</b><br />
<br />
Well, there are more and more frameworks trying to adress the problem of having tons of javascript, whioch becomes a nightmare as well as replace the dynamic generation on the server , thinks such backbone.js, but I find it not very MVC. So why wouldn't I actually write my own MVC framework.<br />
<br />
The first thing we want to do it actually list the users on the page, so that means the controller is invoked, it takes the decistion to load the users from the server into the model, and after that send the model to the view to render the users as html. So we have an html file index.html<br />
<br />
...<br />
<pre class="brush: html"><body>
<div id="list">
<a href="http://www.blogger.com/blogger.g?blogID=9648422#" id="add" onclick="controller.add();">Add</a>
<div id="usersList">
</div>
</div>
<div id="form">
</div>
<div id="tests">
<h1 id="qunit-header">
mvc.js</h1>
</div>
</body>
<h2 id="qunit-userAgent">
</h2>
<ol id="qunit-tests">
</ol>
</pre>
<script>
jQuery(window).load(function () {
controller = new UsersController();
controller.index();
});
</script>
</body>
</html></pre>
<pre class="brush: html"> </pre>
<pre class="brush: html"></pre>
now the <b>controller </b>class:<br />
<br />
<pre class="brush: javascript">UsersController.prototype = new Object;
//constructor definition
UsersController.prototype.constructor = UsersController;
function UsersController() {
this.model = new UsersModel();
this.view=new UsersView();
};
UsersController.prototype.index=function(){
this.model.loadUsers();
this.view.renderUsersList(this.model);
};
</pre>
and the <b>model</b>:<br />
<br />
<pre class="brush: javascript">UsersModel.prototype = new Object;
//constructor definition
UsersModel.prototype.constructor = UsersModel;
function UsersModel()
{
this.users=[];
}
UsersModel.prototype.loadUsers=function()
{
this.users = eval(new HttpRequest().get("http://localhost/users/list.aspx"));
};</pre>
<pre class="brush: javascript"> </pre>
now let's see the <b>view </b>that renders the list. For it we actually use jQuery templates:<br />
<br />
<pre class="brush: javascript">UsersView.prototype = new Object;
//constructor definition
UsersView.prototype.constructor = UsersView;
function UsersView()
{
//templates
this.userLine = "<div if='user${user.id}' class='user' >"+
" <div id='fullName${user.id}' class='cell'>${user.fullName}</div>"+
" <div id='username${user.id}' class='cell'>${user.username}</div>"+
" <div id='email${user.id}' class='cell'>${user.email}</div>"+
" <div class='cell'><a id='edit${user.id}' href='#' onClick='controller.edit(${user.id})'>Edit</a></div>"+
" <div class='cell'><a id='delete${user.id}' href='#' onClick='controller.delete(${user.id})'>Delete</a></div>"+
"</div>";
jQuery.template("userLine", this.userLine);
}
UsersView.prototype.renderUsersList=function(model)
{
jQuery("#usersList").html("");
jQuery("#list").show();
jQuery("#form").hide();
for(var i=0;i<model.users.length;i++)
{
jQuery.tmpl("userLine", { user: model.users[i] }).appendTo("#usersList");
}
};
</pre>
Hmmm, very simple. Now let's implement the add:<br />
<br />
<pre class="brush: javascript">UsersController.prototype.add=function(){
this.view.renderAdd();
};
UsersController.prototype.save=function(id,fullName,username,password,email){
id=this.model.users.length+1;
this.model.users.push({id:id,fullName:fullName,username:username,password:password, email:email});
};</pre>
<pre class="brush: javascript"> </pre>
and in the view:<br />
<br />
<pre class="brush: javascript">UsersView.prototype = new Object;
//constructor definition
UsersView.prototype.constructor = UsersView;
function UsersView()
{
//templates
this.userLine = "<div if='user${user.id}' class='user' >"+
" <div id='fullName${user.id}' class='cell'>${user.fullName}</div>"+
" <div id='username${user.id}' class='cell'>${user.username}</div>"+
" <div id='email${user.id}' class='cell'>${user.email}</div>"+
" <div class='cell'><a id='edit${user.id}' href='#' onClick='controller.edit(${user.id})'>Edit</a></div>"+
" <div class='cell'><a id='delete${user.id}' href='#' onClick='controller.delete(${user.id})'>Delete</a></div>"+
"</div>";
jQuery.template("userLine", this.userLine);
this.userForm = "<div class='fields' style='display:table-row'>"+
" <div id='fullName_${user.id}' style='display:table-row'>Full Name:<input id='fullName' type='text' value='${user.fullName}'/></div>"+
" <div id='username_${user.id}' style='display:table-row'>Username:<input id='username' type='text' value='${user.username}'/></div>"+
" <div id='password_${user.id}' style='display:table-row'>Password:<input id='password' type='password' value='${user.password}'/></div>"+
" <div id='email_${user.id}' style='display:table-row'>Email:<input id='email' type='text' value='${user.email}'/></div>"+
" <a id='save' href='#' onClick='controller.save(\"${user.id}\",jQuery(\"#fullName\").val(),jQuery(\"#username\").val(),jQuery(\"#password\").val(),jQuery(\"#email\").val());'>Save</a>"+
" <a id='cancel' href='#' onClick='controller.list();'>Cancel</a>"+
"</div>";
jQuery.template("userForm", this.userForm);
}
...
UsersView.prototype.renderAdd=function()
{
var user={fullName:"",username:"",password:"",email:"" };
jQuery("#form").html("").show();
jQuery("#list").hide();
jQuery.tmpl("userForm", { user: user}).appendTo("#form");
};
</pre>
Now what about edit?<br />
<br />
<pre class="brush: javascript">UsersController.prototype.edit=function(id){
var user = this.model.findUserById(id);
this.view.renderEdit(user);
};
UsersController.prototype.save=function(id,fullName,username,password,email){
if(id=="")
{
id=this.model.users.length+1;
this.model.users.push({id:id,fullName:fullName,username:username,password:password, email:email});
}
else
{
var user = this.model.findUserById(id);
user.fullName=fullName;
user.username=username;
user.password=password;
user.email=email;
}
this.view.renderUsersList(this.model);
};
</pre>
<br />
and in the model:<br />
<br />
<pre class="brush: javascript">UsersModel.prototype.findUserById=function(id)
{
for(var i=0;i<this.users.length;i++)
{
if(this.users[i].id==id)
return this.users[i];
}
return null;
};
</pre>
and the view is refactored to:<br />
<br />
<pre class="brush: javascript">UsersView.prototype = new Object;
//constructor definition
UsersView.prototype.constructor = UsersView;
function UsersView()
{
//templates
this.userLine = "<div if='user${user.id}' class='user' >"+
" <div id='fullName${user.id}' class='cell'>${user.fullName}</div>"+
" <div id='username${user.id}' class='cell'>${user.username}</div>"+
" <div id='email${user.id}' class='cell'>${user.email}</div>"+
" <div class='cell'><a id='edit${user.id}' href='#' onClick='controller.edit(${user.id})'>Edit</a></div>"+
" <div class='cell'><a id='delete${user.id}' href='#' onClick='controller.delete(${user.id})'>Delete</a></div>"+
"</div>";
jQuery.template("userLine", this.userLine);
this.userForm = "<div class='fields' style='display:table-row'>"+
" <div id='fullName_${user.id}' style='display:table-row'>Full Name:<input id='fullName' type='text' value='${user.fullName}'/></div>"+
" <div id='username_${user.id}' style='display:table-row'>Username:<input id='username' type='text' value='${user.username}'/></div>"+
" <div id='password_${user.id}' style='display:table-row'>Password:<input id='password' type='password' value='${user.password}'/></div>"+
" <div id='email_${user.id}' style='display:table-row'>Email:<input id='email' type='text' value='${user.email}'/></div>"+
" <a id='save' href='#' onClick='controller.save(\"${user.id}\",jQuery(\"#fullName\").val(),jQuery(\"#username\").val(),jQuery(\"#password\").val(),jQuery(\"#email\").val());'>Save</a>"+
" <a id='cancel' href='#' onClick='controller.list();'>Cancel</a>"+
"</div>";
jQuery.template("userForm", this.userForm);
}
UsersView.prototype.renderUsersList=function(model)
{
jQuery("#usersList").html("");
jQuery("#list").show();
jQuery("#form").hide();
for(var i=0;i<model.users.length;i++)
{
jQuery.tmpl("userLine", { user: model.users[i] }).appendTo("#usersList");
}
};
UsersView.prototype.renderAdd=function()
{
var user={fullName:"",username:"",password:"",email:"" };
this.renderForm(user);
};
UsersView.prototype.renderEdit=function(user)
{
this.renderForm(user);
};
UsersView.prototype.renderForm=function(user)
{
jQuery("#form").html("").show();
jQuery("#list").hide();
jQuery.tmpl("userForm", { user: user}).appendTo("#form");
};
</pre>
<br />
<b>Conclusion</b><br />
<br />
As you can see above, implementing MVC in javascript can be very simple and doesn't require any sort of frameworks. Once MVC is implemented the code is well organized depending on concerns: who commands - controller, where by one look you can see what the entire code does (list, add, save, edit)
the model who holds the data and exchanges it with the server and the view which actually renders it in html when asked by the controller.Simple to implement, well organized, easy to follow the code, very extensible. <div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0tag:blogger.com,1999:blog-9648422.post-13728211191425742252012-09-14T11:07:00.002+03:002012-09-14T11:22:50.235+03:00TDD - scurta introducere practica (romanian only)O scurta intoducere practica in Test Driven Development. <br />
<br />
<a href="http://www.danbunea.ro/blogspot/tdd/TDD%20scurta%20introducere%20practica.pdf" target="_blank">Download prezentare ca pdf</a><br />
<a href="http://www.danbunea.ro/blogspot/tdd/tdd%20demos/unit.html" target="_blank">Demo unit test</a><br />
<a href="http://www.danbunea.ro/blogspot/tdd/tdd%20demos/integration.html" target="_blank">Demo integration test</a><br />
<br />
Articole despre TDD:<br />
<a href="http://danbunea.blogspot.ro/2005/12/test-little-code-little-practical.html">http://danbunea.blogspot.ro/2005/12/test-little-code-little-practical.html</a><br />
<a href="http://danbunea.blogspot.ro/2007/01/test-first-web-applications-tdding.html">http://danbunea.blogspot.ro/2007/01/test-first-web-applications-tdding.html</a><br />
<a href="http://danbunea.blogspot.ro/2006/05/refactoring-legacy-web-application.html">http://danbunea.blogspot.ro/2006/05/refactoring-legacy-web-application.html</a><br />
<a href="http://danbunea.blogspot.ro/2005/12/tdding-sales-report.html">http://danbunea.blogspot.ro/2005/12/tdding-sales-report.html</a><br />
<a href="http://danbunea.blogspot.ro/2005/11/model-view-presenter-is-testing_27.html">http://danbunea.blogspot.ro/2005/11/model-view-presenter-is-testing_27.html</a><br />
<a href="http://danbunea.blogspot.ro/2007/03/testing-dragndrop-with-watin.html">http://danbunea.blogspot.ro/2007/03/testing-dragndrop-with-watin.html</a><br />
<a href="http://danbunea.blogspot.ro/2009/05/why-bdd-can-it-help-me.html">http://danbunea.blogspot.ro/2009/05/why-bdd-can-it-help-me.html</a><br />
<br />
<a href="http://danbunea.blogspot.ro/2008/05/chapter-6-quality-and-testing.html">http://danbunea.blogspot.ro/2008/05/chapter-6-quality-and-testing.html</a><br />
<a href="http://danbunea.blogspot.ro/2005/09/how-tdd-improves-development-speed-and.html">http://danbunea.blogspot.ro/2005/09/how-tdd-improves-development-speed-and.html</a><br />
<a href="http://danbunea.blogspot.ro/2006/06/to-obtain-good-code-writing-tests-and.html">http://danbunea.blogspot.ro/2006/06/to-obtain-good-code-writing-tests-and.html</a><br />
<br />
<br /><div class="blogger-post-footer"><!-- Start of StatCounter Code -->
<script type="text/javascript" language="javascript">
var sc_project=646156;
var sc_partition=5;
var sc_security="48e35d80";
</script>
<script type="text/javascript" language="javascript" src="http://www.statcounter.com/counter/counter.js"></script><noscript><a href="http://www.statcounter.com/" target="_blank"><img src="http://c6.statcounter.com/counter.php?sc_project=646156&java=0&security=48e35d80" alt="free log" border="0"></a> </noscript>
<!-- End of StatCounter Code --></div>Dan Buneahttp://www.blogger.com/profile/02360135573826474086noreply@blogger.com0