Using functional programming concepts to write readable code
Functional programming is gaining popularity with more modern languages including functional programming features.
Functional programming is a programming paradigm to build software with functions as building blocks. Functional programming solves the problems introduced by shared state and mutable data by completely eliminating them. Few other advantages include, better reusability of functions. As they are not bound to any specific data, it’s possible to build smaller functions and compose them to achieve any specific functionality.
While it might not be feasible to move an existing software to a completely new functional programming language, but it is possible to include pieces of functional programming in our existing code base when the language provides such features. The main advantage of this is cleaner, better readable code.
For this same reason, I will like to go through one such functional concept which can be achieved by today’s programming language. For the purpose of this post I’ll be using Kotlin. It is ok if you don’t know Kotlin, the concepts can be applied to any language that provide similar features.
Let’s say we are working on system that deals with payments, and each payment for an order is a transaction. This transaction is represented by an object Transaction, and it would have a status representing the state of the transaction which could be in one of Success, Error, Blocked or Pending.
Consider the following code,
var newStatus = successService.tryApplyFor(transaction)
if(newStatus == Status.Pending) {
newStatus = errorService.tryApplyFor(transaction)
if(transaction.status == Status.Pending) {
newStatus = blockedService.tryApplyFor(transaction)
}
}In this particular case the logic of determining each status of transaction resides inside separate services. SuccessService can only tell if the transaction is successful, if not it would return transaction’s existing status, which is Pending in normal cases. Similarly, the errorService can only tell if the transaction is in an error status and so on.
So in the above code, we run our transaction through the successService, if the transaction is still pending, we run it through the errorService, if it is still pending, we run it through blockedService.
While this code looks simple, and probably easier to understand, it is not maintainable. Adding new status means adding another nested if.
Let’s park this code for a bit and go through few functional programming concepts that would help make this code a bit cleaner.
Function composition
When you base your programming with functions as building blocks, there needs to be a way to glue the functions together to build something bigger. One way to do that is using function composition.
A simple example of function composition from Haskell.
add2 a = a + 2
-- add2 :: Num a => a -> a
-- add2(3) returns 5
square a = a * a
-- square :: Num a => a -> a
-- square(2) returns 4squaredWith2 = (square . add2)
-- squaredWith2 :: Num c => c -> c
-- squaredWith2(3) returns 25
In the above code, everything after `—` is a comment, add2 is a function that takes an integer/number and returns an integer/number. So is square. Now when we compose square with add2 using the (.) syntax, type of the resulting function is also the same, it takes an integer and returns an integer.
When we compose a function with another, as in squaredWith2, the resulting function takes an argument, first passes it to the inner most function add2, the result of which is passed onto the next outer function square. So the resulting operation for an argument a is (a+2) * (a+2)or square(add2 a). And it’s possible to compose multiple functions, as in
doAHugeTask a = (add2 . add3 . square . minus2)
-- is same as add2(add3(square(minus2)))In a nutshell, that’s how function composition works, for further reading I would suggest https://wiki.haskell.org/Function_composition.
Monads
Haskell has a Maybetype, which is a perfect example of a monad. The Maybe can hold either value like Just 10or Nothing. As the name suggest it may have a value or not. Maybe types exist in all object oriented language, it’s just that they are not called Maybe, instead it’s implicit. Any variable of non-primitive type, can either hold a value or null.
//Java
MyCustomObject obj;
obj = null;
obj = MyCustomerObject();//Haskell
a = Nothing
a = Just 3
Maybe is useful in cases when certain computations can result in a failure or success. For example, division of two number, when the denominator is zero, you could return a Nothing, if not you return just the value.
//Haskell, div.hs
divide a 0 = Nothing
divide a b = Just a/bAs you can see above, the Maybe type tells 2 things. An indication that the computation failed or succeeded. The result of a successful computation. And that, is a Monad.
To define, a Monad is a wrapper data type, which holds a value and the information about the status of a computation. Other example of a Monad is a class Result, which can be class Success : Result or class Failure : Result, with the value of the result inside the Successclass and an error description inside a Failureclass.
Functor
Now that we briefly know what monad and function composition are, we can combine them to achieve something wonderful. Let’s say you have a function that takes a Monad, applies some logic only if there is a value in it or else just returns the argument as is. For example
//Haskell, add2.hs
add2 Nothing = Nothing
add2 (Just a) = Just (a+2)We defined a function that knows how to apply logic to a wrapped data type, the Monad.
Following the similar approach, if you had to introduce another function called subract2 for the Maybe type,
//Haskell, sub2.hs
sub2 Nothing = Nothing
sub2 (Just a) = Just (a-2)This might get cumbersome, when you also have to define add2 or sub2 for a primitive type, you would end up with redundant functions. So in order to avoid creating separate functions for the primitive type and the wrapper type, you could define a function that takes your function and applies to it the value inside you wrapper,
//Haskell. mapply.hs
add2 a = a + 2
monadapply f Nothing = Nothing
monadapply f (Just a) = Just (f(a))Now, we can reuse the add2 function to do our addition both on a Number and also on a Monad. This function is already provided in Haskell, it’s called the fmap. This function knows how to deal with the Maybe type. And when you write your own Monads, you need to provide an implementation for fmap.
So when your Monad provides an implementation for fmap, it becomes a Functor.
When we combine functors and function composition, we can build a chain of functors, like a pipeline. You start with a value, it get’s processed and is sent to the next function in the chain until the end. The chain is broken when any of the functions within chain return a Failure.
Going back to our initial problem, and applying these concepts with a slight modification.
We start with something like a status Functor and a function composer that calls the next function in the chain when the status is still pending.
interface Status {
fun otherwise(block: () -> Status): Status {
return this
}
}class Pending : Status {
override fun otherwise(block: () -> Status): Status {
return block()
}
}class Success: Status
class Error: Status
class Blocked: Status
Here Status is like a Monad, and the function otherwise is a combination of both fmapand function composition operator (.), so it’s kind of like a Functor. Now we can chain all functions that return a transaction status, and the code would look like,
var newStatus = successService.tryApplyFor(transaction)
.otherwise { errorService.tryApplyFor(transaction) }
.otherwise { blockedService.tryApplyFor(transaction) }The code reads for it self, otherwise takes in a block(or function), and only when the status is Pending, the block is executed else the status is returned as is.
As you can see, applying some bit of functional programming concepts while working with object oriented programming language, has improved the readability to a greater extent, even for someone who doesn’t have much experience with functional concepts.
And so #cleancode
Disclaimer: The above example is one of the possible implementations. There are other ways of solving. Let me know in the comments if you feel there is a better way to do this.
- http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html is a great resource that explains above mentioned concepts using pictures.
- https://wiki.haskell.org/ has lots of resources explaining things functional in Haskell.
