This looks pretty good. I can explain them too:
Keep your building blocks small in scope, for example, a function is a smaller unit than a class. When things are smaller, they are easier to reason about, and it creates better reuse opportunity, since you can reuse things at a finer grain.
Your building blocks shouldn’t depend on the outside environment. Let them be self-contained. Create a clean boundary, so that they can be called independently of the context they were designed in. For example, pure functions only use input passed into their arguments, they do not assume any implicit class fields or global variables to be present.
Stringent building blocks with no customization create rigid systems, that can’t easily be reused in similar use case with only minor differences. In short, make sure your building blocks are always as generic as possible, try to do the right thing for whatever the input is, and allow some level of parameterization. The
map function for example is very malleable, as the user can choose the mapping function, and it knows how to map over all types of collections. Thus making it much more useful then a
Building blocks that are designed together in ways where we know they can be combined to create more complex behavior are best. For example, by using a common sequence abstraction, and by convention making all sequence functions take the sequence as the last argument and return a sequence, we can now use the thread last macro
->> to seamlessly compose a pipeline of transformation. Composition is about compatibility between inputs and outputs of your building blocks, and mechanism to arrange them in any order.
Embracing Lisp in your building blocks brings many benefits. It opens up an easy path to macros, meaning that you can create building blocks that generate other building blocks. A Meta building blocks of some sort. It also means you get to work with your building blocks in a dynamic environment, where you can make changes to your blocks or their arrangement and see the effects of the change in real time. You also get a simple syntax which lets you do easy structural modifications to the code, helps static analyser parse your code for whatever linting, formatting or rewriting you want. And let’s you express all sort of building blocks coherently, without feeling ackward in the existing syntax. Finally, it lets you define convenient data literals, for a more homoiconic representation where what you see is what you get.
Finally, you want to make sure that your building blocks as well as constructions made out of those can be extended externally by their users. Constructs that are closed for extensions don’t benefit reuse as well, since anytime a user face a case which is unsupported by the construct, they have no way to easily make the changes only where necessary to add the missing support back in. Instead they need to either start all over from scratch, or clone and fork your code. Extensibility let’s you avoid these issues, and conveniently let’s you customize or extend support of a construct to more context without changing the construct itself. Protocols for example lets you add support for additional types which wasn’t initially provided. Higher order functions and multi-methods let’s you tweak the behavior of certain parts of the construct, allowing you to reuse all that is common, and only change what needs to differ.