Functional Programming 101: Currying Pattern
Carlos Gómez Suárez
Posted on September 29, 2020
Core Concepts of Functional Programmming.
My story with OOP and Functional Programming
The first programming language I learn was Java, so obviously I needed to learn the Object Oriented Programming (called OOP from now on) too, although today Java allows some Functional Programming concepts too.
When I was learning the core concepts of OOP I sat down on my desktop and I was reading things like encapsulation, yeah, inheritance, oh yeah, and suddenly "Dude, what the heck is polymorphism?". The concept was painful at first time, but applying it was easier than I thought. After time, I learn Python using POO (it was a weird result), a bit of C#, C++, Ruby... I mean, I explored just using OOP. And finally, I learned JavaScript, and yeah, I use OOP again. For any reason, on JavaScript the OOP don't convinced me at all (I was bored to use it too). I think that the JavaScript versatility get lost when I use OOP. Then ES6 appears on my life and it changes everything. I noticed that ES6 allowed Functional Programming, so I decided to learn about using JavaScript (with TypeScript) with functional programming paradigm. When I was learning the core concepts of functional programming I sat down on my desktop and I was reading things like pure functions, yeah, high order functions, and suddenly "Dude, what the heck is a currying function?". Again, the concept was painful at first time, but applying it was easier than I thought.
Today, I will explain you what is a currying function with my own words using TypeScript in this section called "Functional Programming 101".
Core concept of Currying Function
The currying function is a function that returns another function which takes only one parameter at a time.
Currying is a transformation of functions that translates a function from callable as f(a, b, c) into callable as f(a)(b)(c). [1]
function currying(a) {
return function(b) {
// do somethig with the assigned 'a' var
// by using another function that pass 'b' var.
return a + b; // for example
}
}
This is a very simple example that you could found searching on the web.
So if we do something like:
console.log(currying(1)); // function currying(b)
we obtain as a result a function. Everything is ok here. So, has sense if we do:
console.log(currying(1)(1)) // 2
The currying concept works thanks to the JS closures.
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time. [2]
Advantages
This simple concept is so powerful when you need to use it, and the code is cleaner. Maybe for some librabries, using exported currying functions would be a great idea (if it is possible) or in some scenarios it allows more flexibility.
Disavantages
Currying is not common when we're solving problems. Well, In my case I used it a few times, specifically on Factories.
Our first Currying Function using JavaScript
// No curried function
const sumThreeNumbers = (a, b, c) => (a + b + c);
// You designed a function that always will suon only three numbers.
// But what if I need a sum four numbers? or 'n' numbers?
console.log(sumThreeNumbers(1, 2, 3)); // 6
// Curried
const add = (a) => (function(b) { return a + b} );
// I can sum as I want without depend of the number of arguments.
console.log(add(add(1)(2))(3)); // 6
console.log(add(add(add(1)(2))(3))(4)); // 10
But this code looks a little bit confusing. So, I will improve it, but this time using TypeScript.
Improving our first Currying Function using TypeScript
There are two proposals to improve our first currying function. The first one is cool, but the second one is my favorite.
By saving state
This example looks much like a very similar to the core concept and I don't needed to design a currying function that returns a limited curried functions to sum a exactly 'n' times.
const add = (...a: number[]): Function => {
function curried(...b: number[]) {
return add(...a, ...b)
}
// Just saving the 'state' to the returned value.
// Remeber that Functions are objects too in JS.
curried.done = a.reduce((result: number, value: number) => result + value;
return curried;
}
// I designed a nice currying sum by saving the state.
console.log(add(1)(2)(3)(4)(5)(6).done); // 21
It works fine, but I have one problem: I'm using a object and I want use only functions. So, here is the second proposal to improve our currying function.
By use recursion
This case designed to use the passed function until it detect there's no more arguments given.
const curryUntilHasNoArguments = (functionToCurry: Function): Function => {
const next = (...args: any[]) => {
// I tried to avoid use any[] without spread the var with no success.
return (_args: any[]) => {
if (!(_args !== undefined && _args !== null)) {
return args.reduce((acc, a) => {
return functionToCurry.call(functionToCurry, acc, a)
}, 0);
}
return next(...args, _args);
};
};
return next();
};
const add = curryUntilHasNoArguments((a: number, b: number) => a + b);
// Don't forget end with '()' to tell that there's no more arguments.
console.log(add(1)(3)(4)(2)());
Real-world example
Finally, I want to finish this article solving a 'real-world' problem (kind of). The sum currying example is trivial and I used it just with demonstrative purposes.
Logger
enum Method {
WARN = "warn",
ERROR = "error",
LOG = "log",
DEBUG = "debug",
INFO = "info"
}
function createLogger(name: string, ): Function {
return function(action: Method){
return function print(message: string): void {
console[action](`[${new Date()}] [${name}] ${message}`);
}
}
}
const logger = createLogger("Curry");
logger(Method.DEBUG)("This is a debug"); // [Dummy Date][Curry] This is a debug
You could avoid a lot of 'if' by using this kind of logger implementation.
// Dummy scenario
const response = await api.call();
const {metadata, message} = response;
createLogger(api.name)(getMethod(metadata))(message);
function getMethod(metadata: ApiMetadata): Method {
// do something with the metadata to return a valid Method.
switch (metadata){
case metadata.fail: return Method.error;
}
}
Resources.
Posted on September 29, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.