DEV Community

Jaime López
Jaime López

Posted on

Currying to make readable code

How to make code easier to read applying the currying technique

Some years ago I read the book Clean Code by Robert C. Martin and found it very useful. Almost all the things I read seemed to be very obvious but I didn't apply them until this moment.

This book made me realized that my code could not be as good as I thought for other developers even if I was applying the features and functionalities of that programming language. This opened my mind and think of, maybe, a good code was the one that, apart from taking into account performance, could be the easiest to read for others.

Now I'm involved in a simple personal project and want to share with you how I implemented a parser functionality.

Setting the basis

My project has the aim to let the user change the scale of the current temperature based on her location. One of my modules must be an object to transform the scale of the current value to another scale, let's say change Celsius to Fahrenheit or Fahrenheit to Celsius.

The first thing I defined is how transformation methods will work:

  • One parameter with the value to be transformed
  • Return a unique value

To understand this in a visual way, all transformation methods must be something like this:

function(value) {
    return value;
}
Enter fullscreen mode Exit fullscreen mode

Implementation

So, the second step was to implement an object to manage all these transformation methods and avoid them to be living as independent methods in the main script. You might have done something really similar to this so let me share the code and explain it briefly.

const TransformationMethodsHandler = function() {
  let collection = {};

  return {
    // Returns the transformation method by getting the source and target types
    get: function(source, target) {
      if(!collection[source] || !collection[source][target]) {
        return null;
      }

      return collection[source][target];
    },

    // Registers a new tranformation method using source and target as keys
    register: function(source, target, formula) {
      if(!collection[source]) {
        collection[source] = {};
      }
      if(!collection[source][target]) {
        collection[source][target] = {};
      }

      // Added transform property to make it more usable and readable from code
      collection[source][target].transform = formula;

      // Makes easier to keep on registering new methods
      return this;
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

As you can see in register function, I need the type of the source value and, also, the type of the target value to register a transformation method, both values are used as keys. Seems so simple so let me show you how I register the methods for Celsius, Fahrenheit and Kelvin scales:

let transformationHandler = new TransformationMethodsHandler()
  .register(CELSIUS_SCALE, KELVIN_SCALE, (degree) => degree + 273.15)
  .register(KELVIN_SCALE, CELSIUS_SCALE, (degree) => degree - 273.15)
  .register(CELSIUS_SCALE, FAHRENHEIT_SCALE, (degree) => (9*degree/5) + 32)
  .register(FAHRENHEIT_SCALE, CELSIUS_SCALE, (degree) => (5*(degree-32)) / 9)
  .register(FAHRENHEIT_SCALE, KELVIN_SCALE, (degree) => (5*(degree-32)) / 9 + 273.15)
  .register(KELVIN_SCALE, FAHRENHEIT_SCALE, (degree) => (9*(degree - 273.15)/5) + 32);
Enter fullscreen mode Exit fullscreen mode

In addition to my explanation, I've set register function to return the object itself to make easier to register all the transformation methods. My code seems to be nicer registering all the scales. You can add more methods even with different scales or types, for example, we can mix temperature scales with length types (meters and miles) in the same handler.

Once we've initialized our handler object we are able to start using it. Let say a user has introduced a value or you read it from an API call, how can we use the handler object to transform it to the proper value?

let celsius = 24;
let fahrenheit = transformationHandler.get(CELSIUS_SCALE, FAHRENHEIT_SCALE).transform(celsius);
let kelvin = transformationHandler.get(FAHRENHEIT_SCALE, KELVIN_SCALE).transform(fahrenheit);

console.log(celsius, fahrenheit, kelvin); // 24, 75.2, 297.15
Enter fullscreen mode Exit fullscreen mode

That's cool, right? We can change the temperature scale without doing any effort apart from retrieving the right transformation method. I think this is very useful and let coders save time and be sure about the transformation methods they are using. In case of a method were retrieving a wrong value, they just need to fix the error when registering the tranformation method. Try yourself adding some more transformation methods and changing values to different scales or types.

Anyway, I'm not feeling right with this code, seems to be right but is difficult to read. Could it exist a way to make it easier to read?

Applying Currying technique

I would like to have a different code in order to let other coders read and understand in an easier way than the code before. Would it be possible to implement the following line of code?

let newValue = Transformer(value).from(source).to(target);
Enter fullscreen mode Exit fullscreen mode

Well, here comes one of the advanced javascript techniques we get in our hands, currying. For those who don't know what is currying you have a lot of web articles explaining this technique, link. My brief explanation is Currying allows creating functions that return functions that use parameters or variables from the caller function. This is a quick explanation but summarizes what I will show you in the following lines.

In this step, we need to create a new object called Transformer that returns an object with just one method: from. The from method has to return an object with a to method and this is the one that must return the transformed value. Let see in action in the following code:

let Transformer = (function(value) {
    return {
        from: function(source) {
            return {
                to: function(target) {
                    return transformationHandler.get(source, target).transform(value);
                }
            };
        },
    };
})();
Enter fullscreen mode Exit fullscreen mode

The first time you read something like that could be a bit crazy until you understand how it works. This is a really simple currying function that solves the problem we are facing off, code easy to read.

Now, our code can be changed in a simpler way:

let celsius = 24;

// CODE DIFFICULT TO READ
let fahrenheit = transformationHandler.get(CELSIUS_SCALE, FAHRENHEIT_SCALE).transform(celsius);
let kelvin = transformationHandler.get(FAHRENHEIT_SCALE, KELVIN_SCALE).transform(fahrenheit);

// CODE THAT IT IS EASIER TO READ
fahrenheit = Transformer(celsius).from(CELSIUS_SCALE).to(FAHRENHEIT_SCALE);
kelvin = Transformer(fahrenheit).from(FAHRENHEIT_SCALE).to(KELVIN_SCALE);

console.log(celsius, fahrenheit, kelvin); // 24, 75.2, 297.15
Enter fullscreen mode Exit fullscreen mode

We can get some more advantage of the currying technique. Let's think of showing the current temperature but in all the scales available. Currying can help us simplify the process. Remember that the from method returns an object we can use. In this case, we can assign it to a variable and then use it to get the values for the different scales.

let transformFromCelsius = Transformer(celsius).from(CELSIUS_SCALE);
fahrenheit = transformFromCelsius.to(FAHRENHEIT_SCALE);
kelvin = transformFromCelsius.to(KELVIN_SCALE);

console.log(celsius, fahrenheit, kelvin); // 24, 75,.2, 297.15
Enter fullscreen mode Exit fullscreen mode

Summary

To wrap up, you have seen in this article how to register methods to transform values from one type to another and handle them in an object. You've also seen that even having a good implementation of functionality, we can go further and make our code easier to read for other developers using advanced techniques like currying.

I hope you found this article interesting and useful. Let me know your thoughts about it. Here you have the gist.

Top comments (0)