CommonLounge Archive

Functional Basics: Currying, Arrows and Callbacks

March 19, 2018

Introduction

Functions are the backbone of JavaScript development, and nicely enough they are treated as first-class citizens, just like any other variable. As a result, there are several useful ways to use and implement them to make your code more organized and better written for the types of problems you are solving.

While many of these things are considered short-cuts, most of them can influence the way you approach problems in JavaScript.

Arrow functions

Arrow functions in JavaScript are one of the most convenient features introduced in the ES2015 specification. At face value, they provide a short-hand way to express functions without the use of the function keyword in it’s definition.

A brief example of an arrow function

const Arrow = () => 'hello';
console.log(Arrow());
// 'hello'

Line 1 is equivalent to

const Arrow = function () {
    return 'hello';
}

The most important part of an arrow functions is the =>. On the left of the arrow is the arguments the function takes, and on the right, the value it returns. An arrow function that takes no arguments simply uses a (), to tell the JavaScript engine to not expect any arguments in this function.

Arrow functions do not have their own context (explained below), but as a result, there are restrictions on the way they can be called and the arguments passed.

The other thing to note is that Arrow functions are generally assigned as values in the JavaScript language. This can be seen on line 1, since we are assigning the arrow function directly as the value of Arrow.

In most cases, when using arrow functions in JavaScript, you will either be assigning it to a const (immutable) variable, as a property of an Object, or passed directly as a callback function.

Heads up: An immutable value is basically any value that is not intended (or should) be re-assigned. The const keyword in JavaScript prevents code from being re-assigned (not including object mutations, like adding or removing object keys or adding items to an array).

To brace, or not to brace…

Arrow functions have some quirks. There are instances where it is required to wrap values in braces in order to return values. This is due to a limitation in the JavaScript engine where it does not understand the boundaries of certain primitive values. For example, the below is not a valid definition.

const Arrow = () => { key: 'value' };
console.log(Arrow()); // 'undefined'

To fix this, you’d need to rewrite line 1 as:

const ArrowLiteral = () => ({ key: 'value' });
// or
const Arrow = () => {
    return {key: 'value'} 
};

As a rule of thumbs, simple string concatenation calls and single function calls will work without needing to wrap the return value with braces. However, when using Boolean logic or returning an object literal, you should rewrite the statement to be surrounded by parentheses.

An object literal, in the JavaScript world, describes any value that is defined in full, i.e. an object made from enclosed {}. Variable values or references can be returned, but not literals without the use of brackets.

const ArrowLogic = () => (true && false);
console.log(ArrowLogic()); // false
const ArrowProxy = () => ['hello', 'world'].join(', ');
console.log(ArrowProxy()); // 'hello, world'

Arguments of a Arrow function

Arguments in an arrow function work in a similar way to regular functions, but with a few drawbacks. One of these drawbacks is that use of strict function keywords like arguments (which returns an array of the arguments passed to the out-most function) does not work.

For example:

function funcWithArgs(){
    console.log(arguments)
}
>> funcWithArgs(1, 2, 3) 
//Outputs → Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]

Whereas for an arrow function, we would get a ReferenceError:

const funcWithArgs = () => console.log(arguments)
>> funcWithArgs() //Uncaught ReferenceError: arguments is not defined

Instead, to pass an array of arguments we must use the spread syntax (...) to combine all the arguments passed as an array.

const combineToCommaString = (...args) => args.join(', ');
console.log(combineToCommaString('hello', 'world!')); // 'hello, world!'

The spread syntax also works for full function definitions, but this is the only way to extract the full list of arguments to a function defined by the arrow statement.

For single arguments (single values), the use of braces is redundant and can simple be expressed like so:

const greetName = name => console.log(`Hello ${name}!`);
greetName('Mr. Crockford');
// 'hello Mr. Crockford!'

On line one, we are using a template literal to return the value of name. Template literals are defined by using ${} inside an expression surrounded by . For example: One of the variables is ${varName}. And another is ${varName2}.

There is no this

Arrow functions inherit the context of the outermost calling function, so they can’t be effectively used as function constructors, or contain their own context value.

const badConstructor = (name) => {
	this.name = name;
}
const thing = new badConstructor('name');
// TypeError: badConstructor is not a constructor

The above code throws a TypeError because by nature, the new keyword only works with functions that the JavaScript engine can interpret as constructors. And because arrow functions do not have their own context value, the engine knows to it can’t be a constructor, thus causing the error to throw.

Take the following example of how scoping works in arrow functions.

function someConstructor(publicValue) {
	this.publicValue = publicValue;
	return {
		scopedCall: () => {
			return this.publicValue;
		}
	}
};
const someObject = new someConstructor('hello world');
console.log(someObject.scopedCall()); // 'hello world'

On line 1 we are defining a constructor that simply sets an public value to the objects it creates publicValue, we then return a simple getter arrow function from line 4 to 8 (using curly brace notation) to return the value of this.publicValue. Despite having it’s code surrounded in braces, which would normally create a this context specific to the function, it’s using the this value of the parent function.

Thus, when we call scopedCall on the created instance, the value returned equates to the value we defined within the context of the parent function.

Callback Functions

Callback functions in JavaScript are functions that execute after a set a piece of code has finished executing. This allows code that may rely on native calls to external resources, to run a set of commands after the operation completes.

Example of a callback function in action

const safeLogArray = (array, callback) => {
	try {
		array.forEach((value) => console.log(value));
		callback();
	} catch (error) {
		callback(error);
	}
}
safeLogArray(['hello', 'world'], (error) => {
	console.log(error ? `log failed - ${error.message}` : 'log completed!');
});
/* outputs:
hello
world
log completed */

On line 1, we are defining a simple arrow function that takes an array and callback parameter. On line 2 we are using try, catch blocks to log the value of array if we can, otherwise fail. The callback is then executed on either line 4 (if the forEach method passes) or on line 6 if the forEach operation fails.

The callback function typically is used for managing response data (or events) and handling errors after a call has completed. Our callback function (passed as the second argument on line 11) will always fire when the function completes due to the try, catch. Line 12, we log the error if there was one, otherwise we log ‘log completed!’

If we attempt to pass a value without a forEach method, we can catch the error thrown and handle it gracefully:

safeLogArray(null, (error) => {
	console.log(error ? `log failed - ${error.message}` : 'log completed!');
});
// log failed - Cannot read property 'forEach' of null

Callbacks, if used improperly, can cause serious design problems in your applications. Be sure to checkout callbackhell.com for a better example of how to manage callbacks (or avoid them when you don’t need them).

Currying

Currying is a somewhat abstract functional programming term that is somewhat confusing to read about. In essence however, currying is simply the act of returning a usable function in JavaScript instead of a primitive value like a Boolean or a string.

Let’s start out with an example:

Basic Currying Example

function functionalThing(noun) {
	return phrase => console.log(`I am ${noun}, and ${phrase}.`);
}
const Personfunction functionalThing(noun) {
	return phrase => console.log(`I am ${noun}, and ${phrase}.`);
}
const Dog = functionalThing('Dog');
Dog('I like to eat');
// 'I am Dog, and I like to eat.'
Dog('sometimes I am able to sleep');
// 'I am Dog, and sometimes I am able to sleep.'
functionalThing('a program')('I like to execute code');
// 'I am a program, and I like to execute code.'

In essence, using a currying function, we can create a scoped wrapper around another function. It allows us to create specialized functions with their own scopes, or has internal values only available within the scope of the calling function: functionalThing. For example, the function Dog was an output of the functionalThing function, and behaves like an ordinary function, beside the scoped noun property.

This becomes especially useful in functional JavaScript, when one is trying to minimize the amount of variables required to process information.

As shown in the last lines of the above example, you do not need assign the functionalThing function to a value, since it’s not a constructor we don’t have to initialize anything besides the function call.

Using currying in functional calls (Array.map)

We can also use currying to produce and assign specialized iterators.

First, let’s cover map.map is an Array method for iterating over its values and re-assigning them based on a passed function (callback). This allows a one-line way to return a modified copy of an array via looping, and it extremely useful for remapping data.

> [1,2,3].map((a) => a+1)
// [2, 3, 4]

Now, lets look at an example involving currying:

const append = string => item => `${item}${string}`;
const array = ['cats', 'dogs', 'they'].map(append(' are outside'));
console.log(array);
// ['cats are outside', 'dogs are outside', 'they are outside']

In the above example, we are using two nested shorthand functions to produce the equivalent of two nested functions (on line 1). The arrow (=>) pointing to that template string (also on line 1) is shorthand for return that string. On line two, we take an array and map over it using our custom append curry function.

Using currying in functional calls (Array.filter)

filter is an Array method for filtering your array. The callback must return a Boolean, with true indicating that the value passed the filter and false indicating that the value didn’t pass, so it shouldn’t be included.

> [1,2,3].filter((a) => a > 1)
// [2, 3]

Now, lets look at an example involving currying:

const onlyEndingWith = endValue => item => (item.slice(-endValue.length) === endValue);
const array = ['cats', 'dogs', 'they'].filter(onlyEndingWith('s'));
console.log(array);
// ['cats', 'dogs']

Similar to the map example, we are using a nested arrow functions to build a filter that can be passed to the Array filter method. We do a simple string comparison on line 1 that compares the end value of the item being filtered with the value of the passed endValue (which can be any length).

Thusly, the method onlyEndingWith will only filter items ending with the specified string.

Using currying in functional calls (Array.reduce)

The reduce Array method works by performing an operation against every element of an array in order to reduce it to a single value. The function passed as an argument to reduce accepts the accumulated value as well as the current value. Let’s look at a quick example:

[1, 2, 3].reduce((totalValue, currentValue) => totalValue + currentValue)
//6

Now, lets look at an example involving currying:

const reduceMultiplyBy = multiplier => (total, current) => (total + (current * multiplier));
const multiplied = [0, 2, 4].reduce(reduceMultiplyBy(2));
console.log(multiplied);
// 12 

In out example above, on line 1 we created a simple curry function reduceMuliplyBy, which acts as a way to map and multiply all the values of a given array together (when passed to the reduce method).

On line 2, we define an array [0, 2, 4]. Because we pass the curried reduceMuliplyBy(2), all the items in the array are reduced and and the index is multiplied by 2.

(total, current) -> total = total + (current * 2)
(0, 0) -> total = 0 + (0 * 2) = 0; // returns 0
(0, 2) -> total = 0 + (2 * 2) = 4; // returns 4
(4, 4) -> total = 4 + (4 * 2) = 12; // returns 12

After the reducer finishes execution, we are left with the value 12. The curry function gave us a simple way to express the exact operation we are using on the Array, which is useful for building custom functional expressions.

Conclusion

After reading this wiki you should have a basic understanding of how currying works, arrow function expressions, and how you can use them to streamline the way you right code. All things, aside all of these features have the potential to help you write code that is suited to solving the specific (niche) problems in data processing, a key feature of functional programming.


© 2016-2022. All rights reserved.