While working on a Clojure application that was for production, rather than being an exercise, I saw that I was using OO programming with the syntax of Clojure. I was having problems coming back to change code that I have done not long before. Testing was an absolute pain, with continuous use of
with-redefs and weird subnesting of
let statements happening all over the place. Clearly, my knowledge, and my internalization of functional programming, wasn't good enough. It was time to start looking into how to improve my skills.
Thanks to my colleagues at Codurance I discovered that at SoCraTes UK 2015 there was a discussion already about Functional Calisthenics, organized by Ian Johnson, and which results you can find at his blog post Introducing Functional Calisthenics.
After a bit of trying, I thought that a few of the rules were not exactly how I would have set them up and lacked clarity about the point of the rules. Therefore, we sat down, a few functional programmers at Codurance, and revised the rules. The below represents the rules after this revision, including how to apply them and what is the expected outcome of learning to use those rules.
These rules are constraints on how to create your code and are only intended to be applied when doing katas or exercises. During the exercises you should follow them to a t. Following this rules on production code is left as a decision for each. I envision to start using them in the order below; some rules build on top of previous rules to achieve better knowledge and understanding of functional programming.
- Name everything
- No mutable state
- Exhaustive conditionals
- Do not use intermediate variables
- Expressions not statements
- No Explicit recursion
- Generic building blocks
- Side effects at the boundaries
- Infinite Sequences
- One argument functions
Most basic one. It will seem harsh for experienced functional programmers. First one to be broken on production code.
All functions need to be named. Which means that you shouldn't use lambdas or anonymous functions. Also, name all your variables as per standard clean code rules.
Learn to recognize patterns on your code. It will help recognizing signatures that repeat, and the applying of DRY on your code. Also, it will clearly express the intention of the action, rather than just show the implementation.
No mutable state
This is the basis of FP.
You shouldn't use any variable of any type that can mutate.
Learn how to create code around immutable variables.
Two main benefits:
- FP is about immutability. Most of its benefits comes from the fact that all your functions are (or should be) referencially transparent.
- Use of recursion as your main looping technique. No for or while loops in your code. For comprehensions abide by this rule.
This rule is mostly preparation work.
There can not be an if without an else. Switches and pattern matching should always have all paths considered (either through default paths or because all options have been considered).
Complete functions. A function should know how to deal with all possible values passed as arguments.
Do Not Use Intermediate Variables
This rule is mostly preparation work.
There shouldn't be any variable declared in the body of a function. Only parameters and other functions should appear on the body.
Use and understanding of functional pipelines. Starting on the path of functional composition. Functions are the building blocks of functional languages and the combination of functions create the logic of your application. Not having intermediate variables means changing the way that you think about your functions, and how the body of a function (composed of other functions) is, so the code remains legible and easy to follow.
Expressions, not Statements
A consequence of the two previous rules. Or a generalization.
All lines should be expressions. That's it, all lines should return a value.
Full purity on the code. Statements that are not expression are there for their side-effects. Nothing like that should be in the code.
No Explicit Recursion
Not as harsh as it looks.
You shouldn't use explicit recursion like Clojure's
java loop/recur forms or F#
java let rec form.
Learning the power of map and reduce and use of High Order Functions. The idea is powerful enough to be the basis of some systems (Apache Hadoop) and has appeared on non-FP languages.
Generic Building Blocks
One of the advantages of FP languages is that is easy to generalize algorithms.
Try to use a much as possible generic types for your functions, outside of the boundaries of your application. Don't use a list, use a collection; Don't use an int when you can use a number; so forth and so on.
Easily composable code.
Using existing functionality and High Order Function provided by your language stack becomes much easier.
Side Effects at the Boundaries
On the original was at the Top Level. We wanted to extend due to advance scenarios (but is standing on thin ice this reasoning)
Side effects should only appear on the boundaries of your application. The guts of your application should have no side effects.
Limit the amount of code that is influenced by side effects. The main outcome is to create code on which you have tight control over side effects, when are the executed and to limit the effect on them on your logic. All your code logic should depend exclusively on parameters provided.
Another harsh rule. One that in production will depend a lot on the optimizations of the language.
All sequences, collections used need to be infinite collections. You can't use collections that need to have a fixed size specified.
Lazy Evaluation of Sequences Lazyness has some considerations regarding perfomance and not performing difficult calculations when they are not needed. Furthermore, reading from a file, database or network can be consideded an infinite sequence.
One argument functions
Personally, I have found this rule the most difficult to apply. There are some cases at the moment that I have found either very difficult or creating a massive number of additional functions.
Each function should have a single parameter. You need to be explicit, just using the fact that the language works by automatically applying currying is not enough (Haskel, F#, ...) The parameter can be a structure/map or some other complex type (don't think this restricts to primitives).
Use of functional composition. Currying and partial function usage.
We have started to use them on katas. They force us to think about how we are coding and force us to move away from our standard OOP mentality. At the moment we see them as a good way to improve our skills.
Of course, this represents the second iteration that we did of the rules. And it is possible that at some point they change. Don't hesitate to contact us to improve them.
We will be adding a pdf version with the rules and we will be showing some code in the near future.