Super DRY Code
DRY means "Don't Repeat Yourself" and is probably one of the most important principles when you create software, however, how far can you take it?
Join the DZone community and get the full member experience.Join For Free
DRY means "Don't Repeat Yourself" and is arguably one of the most important design principles as you create code, regardless of what language you use. The reasons are simple; Because every time you repeat code, you have to modify multiple places if you need to change your logic. Hence, if you don't create DRY code, you get bloated software, repeating the same constructs, to the point where it becomes impossible to maintain. Hence, the very definition of DRY is that your code is reusable, implying you can arguably quantify how DRY your code is, by asking yourself how reusable your code is. OOP however has one fundamental flaw which makes it ridiculously difficult to create DRY code, which is best explained by the following famous quote about reuse in OOP...
You want the banana, but you get a monkey, holding a banana, in the rain forest!
The above basically "encapsulates" (pun!) the problem with OOP as a mechanism to achieve reusability, and if you don't agree with me, please create some more OOP code, until you agree with me, at which point your opinion matters ... ;)
Last week I held a Hyperlambda course for 3 Ukrainian developers explicitly flying in to Cyprus solely to learn Hyperlambda from me, and especially one of my participants was extremely active in regards to giving me suggestions for improvements. Two of his suggestions were particularly interesting though, and I would hence like to share these since they are now a part of the core of Magic. And even if you don't care about Magic, there might still be some lessons to learn from these ideas.
An interceptor in Hyperlambda is (almost) the same as injecting something into your ASP.NET 5 pipeline. Basically, it implies a piece of Hyperlambda code, that wraps your endpoint Hyperlambda, where you can put all commonalities, to make your code more DRY. For instance, 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 these same files in the same folder, and a lot of their code is similar. Besides, it's not really generic in nature, and doesn't allow for changing for instance the name of the database you're using. So let's imagine we want to change the database name, allowing it to be a configuration setting 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 slightly better in regards to the "Open Closed Principle", since we can apply changes to it, without actually having to change the code itself. However, with dozens of such files in the same folder, at some point we will realise we're repeating the same [config.get] and [data.connect] invocation over and over again. As of the latest version of Magic though, we can use "interceptors" to avoid repeating ourselves. Let's illustrate by creating an interceptor replacing our above "wet" code.
config.get:"acme:foo:database-connection-string" data.connect:x:- .interceptor
The above is an interceptor, and the result of creating the above file as an "interceptor.hl" file, is that we now end up avoiding repeating ourselves, since we can now modify 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 is the thing not repeating itself across potentially dozens or hundreds of files, but it's also much more readable. Hence, by intelligently using interceptors, we make it much simpler to read our code, in addition to that our code becomes more DRY. The cost though is that not all code that is being executed is in the same file. This is a small cost for the added readability, and the added reusability I would say.
Now of course, you can also nest interceptors upwards in your folder hierarchy, resulting in that multiple interceptors are recursively applied, allowing for you to create some fairly intelligent snippets of code as a result, where the end code that is executed, is actually the concatenated result of half a dozen of different files, recursively applying themselves upwards in your hierarchy to your interceptor files. Watch me demonstrate the idea in the following video.
In addition to using interceptors in Hyperlambda, you can also use global exception handlers, declared on a per folder level, the same way interceptors are declared. Of course, exception handlers are not nested and applied recursively though, but it's roughly the same structure as interceptors, implying you declare your exception handler once, and all files in that folder and downwards in the hierarchy by default will use your newly created exception handler.
The really cool part of this is that you can actually globally change the behaviour of your application if you wish, by applying a single interceptor and exception handler to your module, allowing you to build either partial products as micro services, and/or change other aspects of your app by combining interceptors and exception handlers with the dynamic features of Hyperlambda. If you want to read more details related to this, you can check out the tutorial style documentation for interceptors 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 such as in for instance ASP.NET Core etc - Is that in Hyperlambda your endpoint file can actually assume the existence of variables and lambda objects, the interceptor is responsible for injecting into the lambda object that is being executed, giving you much more DRY capabilities than OOP, at the cost of having to imagine the things that are not there (yet!) as you create your endpoint Hyperlambda files. The latter I suspect will be a feature I will deeply appreciate myself as time proceeds, and I get more used to these guys. The paradox is that this is arguably "better OOP than OOP and more SOLID code that SOLID", even though there's not a single virtual method, zero interfaces, and null classes to be seen anyway, hence ...
No monkey, no rain forest, yet still a banana ^_^
Or "Super DRY code" ...
- Download Magic here if you want to play around with it ...
Opinions expressed by DZone contributors are their own.