DEV Community

Cover image for Just for fun : transform a simple code into over  engineered functional style code (featuring javascript)
ecyrbe
ecyrbe

Posted on

Just for fun : transform a simple code into over engineered functional style code (featuring javascript)

Hi fellow programmers,

This article is kind of a nice (maybe) and pretty (i hope) exercise. But don't take it too seriously, no need to go into flamewars, take it as a fun exercise i did. Initially i wrote this as a comment in reaction to an article here that was deleted by it's author.
As a disclaimer, i love functional programming, object oriented programming, all kind of styles. They all have their pros/cons and beauty.

So let's begin and and imagine you have a beginner programmer writing this piece of code in javascript:

function daysUntil(day, month) {
  const today = new Date();
  const sameYear = new Date(today.getFullYear(), month - 1, day);
  if (sameYear > today)
    return Math.ceil((sameYear - today) / (1000 * 60 * 60 * 24));
  else {
    const nextYear = new Date(today.getFullYear() + 1, month - 1, day);
    return Math.ceil((nextYear - today) / (1000 * 60 * 60 * 24));
  }
}
console.log(daysUntil(1, 12));
Enter fullscreen mode Exit fullscreen mode

Imagine you are this guy, doing this code maintenance :
lambda guy

Of course the first thing you see when taking ownership of the code is side effects (horror!). So let's quickly fix that mistake :

function daysUntil(day, month, today) {
  const sameYear = new Date(today.getFullYear(), month - 1, day);
  if (sameYear > today)
    return Math.ceil((sameYear - today) / (1000 * 60 * 60 * 24));
  else {
    const nextYear = new Date(today.getFullYear() + 1, month - 1, day);
    return Math.ceil((nextYear - today) / (1000 * 60 * 60 * 24));
  }
}
const today = new Date(); // side effect
console.log(daysUntil(1, 12,today));
Enter fullscreen mode Exit fullscreen mode

Better. Now we can start coding. Because, yes, we got rid of the side effect but come on, this code is not declarative enough. As a functional programming adept, i have to transform the code above into something as simple as this :

// fn(number,number) -> fn(date) -> number
const daysUntil = (day, month) => daysBetween(now, futureDateFrom(day, month));

const today = new Date(); // side effect
console.log(daysUntil(1, 12)(today));
Enter fullscreen mode Exit fullscreen mode

I can read this code and understand what it does, because it reads like a book. I want the days between now and the first December. As simple as that.

So we all heard of, divide and conquer in programming and that's what we are aiming for. So we have in our dreams, a function called futureDateFrom :

// fn(number,number) -> fn(date) -> date
const futureDateFrom = (day, month) => minUpperDate(sameYearOf(day, month), nextYearOf(day, month));
Enter fullscreen mode Exit fullscreen mode

What does futureDateFrom do? It computes the first date in the future given a day and a month. It's a high order function, meaning it's a function that returns a function, and we create this function by composing multiple functions together.

And then we have another function called daysBetween:

// fn( fn(date)->date, fn(date)->date ) -> fn(date) -> number
const daysBetween = (getDateA, getDateB) => (date) =>
  Math.ceil(Math.abs(getDateA(date) - getDateB(date)) / (1000 * 60 * 60 * 24));
Enter fullscreen mode Exit fullscreen mode

It takes two function that take a date and return a date as parameters and return a function that take a date and computes the days between those dates.
We now see that now is a function :

// fn(any) -> any
const now = (x) => x;
Enter fullscreen mode Exit fullscreen mode

And it's the identity function, that returns whatever you give it as a parameter.

The rest of the functions are the following ones, and are simpler to understand, but still high order functions

// fn( fn(date)->date, fn(date)->date ) -> fn(date) -> date
const minUpperDate = (getDateA, getDateB) => (date) =>
  minUpperValue(getDateA(date), getDateB(date))(date);
// fn(number,number) -> fn(date) -> date
const sameYearOf = (day, month) => (date) => dateFrom(day, month, year(date));
// fn(number,number) -> fn(date) -> date
const nextYearOf = (day, month) => (date) => dateFrom(day, month, year(date) + 1);
Enter fullscreen mode Exit fullscreen mode

we see that the missing functions can be implemented like so :

// fn(comparable, comparable) -> fn(comparable) -> comparable
const minUpperValue = (a, b) => (pivot) => (a < pivot && pivot < b ? b : a);
// fn(date) -> date
const year = (date) => date.getFullYear();
// fn(number, number, number) -> date
const dateFrom = (day, month, year) => new Date(year, month - 1, day);
Enter fullscreen mode Exit fullscreen mode

And it's finished. We defined all the functions. We see that we managed to turn a single function into 10 simpler ones. These function have the benefit of being reuseable independant bits of code. All these function can now be unit tested easily.
But it's undeniable that the produced code is harder to read for the non initiated.

Do i think that writing all this code is worth it? Divide and conquer always have proved to make things easier to grasp and test but at the cost of complexity.

So you have to get a feeling at when to stop dividing your code, because it can be a never ending task that is not worth it. And in my experience, it stops being worth it when the divided code is not reused or if the code you want to split can be understood by itself. So as a rule of thumb, always ask yourself if the code you are splitting will be reused or is complex enough to be splitted.

As an exercise, we let you imagine you are in the shoes of this guy and write in Object oriented style an over engineered piece of code :
oop guy

Discussion (0)