DRY means “Do not repeat yourself” and is without a doubt one of the most important design principles when creating code, no matter what language you use. The reasons are simple; Because every time you repeat code, you have to change several places if you have to change your logic. Therefore, if you do not create DRY code, you will get inflated software that repeats the same constructions to the point where it becomes impossible to maintain. Therefore, the very definition of DRY is that your code is reusable, which means you can no doubt quantify how DRY your code is by asking yourself how reusable your code is. However, OOP has a fundamental flaw that makes it ridiculously difficult to create DRY code, which is best explained by the following famous quote about recycling in OOP …
You want the banana, but you get a monkey holding a banana in the rainforest!
The above basic “encapsulates” (puns!) the problem with OOP as a mechanism for achieving recycling, and if you do not agree with me, create another OOP code until you agree with me, at which point your opinion is important …;)
Last week I held a Hyperlambda course for 3 Ukrainian developers who explicitly flew into Cyprus solely to learn Hyperlambda from me, and in particular one of my participants was extremely active in giving me suggestions for improvements. However, two of his suggestions were particularly interesting, and I would therefore like to share these, as they are now part of the core of magic. And even if you do not care about magic, there may still be some lessons to be learned from these ideas.
An interceptor in Hyperlambda is (almost) the same as injecting something into your ASP.NET 5 pipeline. Basically, it involves a piece of Hyperlambda code that encloses your endpoint Hyperlambda, where you can put all the common features to make your code more DRY. For example, imagine the following code.
.arguments name:string data.connect:[generic|some-database] data.read table:users columns username return:x:@data.read/*
The above code is a typical example of a Hyperlambda endpoint file. However, you often have dozens of the same files in the same folder and a lot of their code is the same. Also, it is not really generic and does not allow for example to change the name of the database you are using. So let’s imagine that we want to change the name of the database so that it can be a configuration option instead.
.arguments name:string config.get:"acme:foo:database-connection-string" data.connect:x:- data.read table:users columns username return:x:@data.read/*
OK, so now the code is a little better in terms of “Open closed principle”, as we can apply changes to it without actually having to change the code itself. But with dozens of such files in the same directory, we will at some point realize that we are repeating the same [config.get] and [data.connect] invocation again and again. From the latest version of Magic, however, we can use “listeners” to avoid repeating ourselves. Let us illustrate by creating an interceptor that replaces our above “wet” code.
config.get:"acme:foo:database-connection-string" data.connect:x:- .interceptor
The above is an interceptor, and the result of the creation of the above file as one “interceptor.hl” file, is that we now end up avoiding repeating ourselves, as we can now change the endpoint file to become the following.
.arguments name:string data.read table:users columns username return:x:@data.read/*
We only removed two lines of code from our original Hyperlambda example, but not only are things not repeating themselves across potentially dozens or hundreds of files, but it’s much more readable as well. Therefore, by using interceptors intelligently, we make it much easier to read our code, in addition to our code becoming more DRY. The cost, however, is that not all code executed is in the same file. This is a small price for the extra readability, and the extra reusability I would say.
Now, of course, you can also embed interceptors upwards in your directory hierarchy, resulting in multiple interceptors being used recursively, so you can create some pretty intelligent snippets of code as a result, where the end code executed is actually linked result of a dozen different files , which recursively applies itself upwards in your hierarchy to your eavesdropping files. Watch me demonstrate the idea in the following video.
In addition to using interceptors in Hyperlambda, you can also use global exception handlers specified at a level per. Folder, in the same way as interceptors are declared. Of course, exception handlers are not embedded and used recursively, but it is roughly the same structure as eavesdropping points, which means that you declare your exception handler once, and all files in that folder and down the hierarchy will use your newly created exception handler by default.
The really cool part of this is that you can actually globally change the behavior of your application, if you wish, by applying a single interceptor and exception handler to your module, so you can build either sub-products like microservices and / or modify others aspects of your app by combining interceptors and exception handlers with the dynamic features of Hyperlambda. If you want to read more details in connection with this, you can check the guidance style documentation for listeners at the following link.
One of the crucial differences between the Hyperlambda version of this and the more traditional OOP version where you inject things into your pipeline, e.g. In ASP.NET Core, etc. – Is it in Hyperlambda that your endpoint file can actually assume the existence of variables and lambda objects, the interceptor is responsible for injecting into the lambda object being executed, giving you much more DRY capacities than OOP, at the expense of having to imagine the things that are not there (yet!) while creating your endpoint Hyperlambda files. The latter, I suppose, will be a function I will greatly appreciate in myself as time progresses and I become more accustomed to these guys. The paradox is that this is without a doubt what it is “better OOP than OOP and more SOLID code that SOLID”, although there is still not a single virtual method, zero interfaces and zero classes to see, therefore …
No monkey, no rainforest, but still a banana ^ _ ^
Or “Super DRY code” …