How to structure functional code
Let’s talk about design. In one of my first posts I mentioned composability - now is time to dig a bit deeper. It’s not only functions that compose!
Composition vs inheritance
When I first started my functional programming journey I heard a lot of “composition over inheritance”. Composition, just like inheritance, is a way of thinking and designing your programs that allows you to implement polymorphism. (check my previous post for polymorphism recap)
I also mentioned functional programming is all about a clear division between what (data) and how (behaviour/operations).
If you’re anything like me back in the day you might be thinking now - ok, I know how to compose functions and I know why but… how do I take it to the next step? How do I actually structure my code? Those gorgeous composed functions must be a part of something greater. Does that greater something compose as well or how does it work?
Modules, baby!
If there’s no mutability and behaviours don’t mix with data it’s easy enough to compose that behaviour without nasty surprises down the line. Now, because we speak of how to organize your code, below example is going to be a little lengthy. It shows how to use traits and get the effect of really cool pluggable modules. The idea is basically to have a bare class and add onto it until we have all behaviours that we want. This removes the need for the classic inheritance polymorphism and gives you a powerful tool to mix and match for behaviours. In the below example, notice how I won’t have a problem with inheritance when an unexpected requirement for BathroomBakery
comes in - in classic inheritance I’d be cornered as BathroomBakery
is not a “full” bathroom but also not a “full” Kitchen.
Note: modules is a wide topic that ties up to a lot of other things. It’s touched a lot in a great book called Functional and reactive domain design by D. Ghosh. I might write a blog post just about what this book has to say. It’s really good and I can’t recommend it enough!
But functional programming is the opposite of object oriented so why like this?
What had me wonder for a while in the beginning of my journey was that functional programming is so complex and that it is somehow completely opposite to object oriented programming and whatever we do in object oriented programming is the “less smart” way, evil and should not be done in functional. So NO polymorphism and NO instantiating ANYTHING and functions somehow float in the runtime and do their thing without working on or from within any objects (as silly as it sounds). So again, the standard functional programming journey question followed - how do I even code then?
First things first though - object oriented programming is NOT opposite to functional programming!
Let’s clear that confusion once and for all. Functional programming is as opposite to object oriented just as taking a train is opposite to taking a bus. It’s not at all opposite. It’s different yet serves the same purpose. You get to your destination, it might be more efficient or less efficient, it might be cheaper or more expensive, faster or slower, there can be a fabulous service or no service, it might take a road you know well or bounce off to go along meadows and mountains but it gets you to where you have to be at the end of the day.
So you do create objects, you do structure your code, use dependency injection, instantiate classes, pass arguments to classes etc. - don’t think this is any different. Again, in a simple world functional means no mutability and clear division between data and functions on that data.
A little note on dependency injection
In any grown-up project there is a high chance that your services are useless without repositories and so are repositories without configs. If your app is trivial and only needs a handful of instances dependency injection might be an overkill (this applies to any project though, not only functional). In any other case it is quite beneficial.
In Scala there is a couple of techniques you can use for dependency injection. I will have a separate blog post on them. For now just trust me that the main and most popular ones are:
- self types
- reader monad
- third party tool like MacWire
Real life modules
Let’s leave the rooms and toilet bakeries and move on to something more real life like. Dependency injection is crucial in modular design. Let’s see:
Note on the above code: it’s far from perfect from the design choice point of view. It’s just to demonstrate the idea of modules in a simple way without cornering myself with too much irrelevant code. DatabaseService
is actually a repository that should be abstracted much more but let’s leave this aside.
Explanation of the code (do read comments in the code for more clarity!):
- For any application to work we first we need an application.config and some config reader (config reader is skipped in the example here for simplicity)
- Then we need a config case class to represent it as data
- Then we need a configModule to expose config
- Then we need a databaseService that takes config as arg, we can use it because it’s exposed in ConfigModule
- Then we need a databaseModule to expose dbService
What if I have some functions that don’t belong to any of the modules/services?
Maybe you have a function such as convertXtoY
or extractX
that don’t really belong to any module/service. You could then use Utilities
object and just import whatever is needed whenever you need it. Always keep your modules and services scope clean. Ask yourself questions such as “does updating a list of borrowed books directly relate to Account
?”. If the answer is “yeah, definitely” then updateBorrowedBooks
belongs to AccountService
. If the answer is unclear or “no” then maybe it’s a sign it belongs to another service, or perhaps a utilities object altogether.
Problems with modules
There is no silver bullet and there is a couple of problems with the modular approach. It’s all about being reasonable in picking your approach in design. I leave this as open questions to ponder more than trying to give a ready answer. I will probably pop in to update those with time and experience :)
-
what if we need to mix in two modules/services with the same value/function name? Scala compiler will overshadow earlier occurring one and only using the last occurring one.
-
what if there is a 100 traits we need to mix in? That’s a very bad code readability and a high chance of function naming clashes. Could utility objects help?
Recap
Let’s recap:
- always keep your models (data) separate from your services (functions)
- structure your code into modules which are implemented as traits
- do dependency injection (post on that coming soon)
- only mix modules with modules
- if you have some functions that don’t belong into any modules they could be a good candidate for a
Utility
object
References:
Functional and reactive domain design by D. Ghosh
SoftwareMill blogs and tools like MacWire (http://di-in-scala.github.io/)