A Functional Programmer’s Introduction to JavaScript (Composing Software)

Eric Elliott
Feb 25, 2017 · 13 min read
Image for post
Image for post
Smoke Art Cubes to Smoke — MattysFlicks — (CC BY 2.0)

Note: This is part of the “Composing Software” series (now a book!) on learning functional programming and compositional software techniques in JavaScriptES6+ from the ground up. Stay tuned. There’s a lot more of this to come!
Buy the Book | Index | < Previous | Next >

For those unfamiliar with JavaScript or ES6+, this is intended as a brief introduction. Whether you’re a beginner or experienced JavaScript developer, you may learn something new. The following is only meant to scratch the surface and get you excited. If you want to know more, you’ll just have to explore deeper. There’s a lot more ahead.

The best way to learn to code is to code. I recommend that you follow along using an interactive JavaScript programming environment such as CodePen or the Babel REPL.

Alternatively, you can get away with using the Node or browser console REPLs.

Expressions and Values

The following are all valid expressions in JavaScript:

7;7 + 1; // 87 * 2; // 14'Hello'; // Hello

The value of an expression can be given a name. When you do so, the expression is evaluated first, and the resulting value is assigned to the name. For this, we’ll use the const keyword. It's not the only way, but it's the one you'll use most, so we'll stick with const for now:

const hello = 'Hello';
hello; // Hello

var, let, and const

Sometimes it’s useful to reassign variables. For example, if you’re using manual, imperative iteration rather than a more functional approach, you can iterate a counter assigned with let.

Because var tells you the least about the variable, it is the weakest signal. Since I started using ES6, I have never intentionally declared a var in a real software project.

Be aware that once a variable is declared with let or const, any attempt to declare it again will result in an error. If you prefer more experimental flexibility in the REPL (Read, Eval, Print Loop) environment, you may use var instead of const to declare variables. Redeclaring var is allowed.

This text will use const in order to get you in the habit of defaulting to const for actual programs, but feel free to substitute var for the purpose of interactive experimentation.

Types

An array is an ordered list of values. Think of it as a box that can hold many items. Here’s the array literal notation:

[1, 2, 3];

Of course, that’s an expression which can be given a name:

const arr = [1, 2, 3];

An object in JavaScript is a collection of key: value pairs. It also has a literal notation:

{
key: 'value'
}

And of course, you can assign an object to a name:

const foo = {
bar: 'bar'
}

If you want to assign existing variables to object property keys of the same name, there’s a shortcut for that. You can just type the variable name instead of providing both a key and a value:

const a = 'a';
const oldA = { a: a }; // long, redundant way
const oA = { a }; // short an sweet!

Just for fun, let’s do that again:

const b = 'b';
const oB = { b };

Objects can be easily composed together into new objects:

const c = {...oA, ...oB}; // { a: 'a', b: 'b' }

Those dots are the object spread operator. It iterates over the properties in oA and assigns them to the new object, then does the same for oB, overriding any keys that already exist on the new object. As of this writing, object spread is a new, experimental feature that may not be available in all the popular browsers yet, but if it's not working for you, there is a substitute: Object.assign():

const d = Object.assign({}, oA, oB); // { a: 'a', b: 'b' }

Only a little more typing in the Object.assign() example, and if you're composing lots of objects, it may even save you some typing. Note that when you use Object.assign(), you must pass a destination object as the first parameter. It is the object that properties will be copied to. If you forget, and omit the destination object, the object you pass in the first argument will be mutated.

In my experience, mutating an existing object rather than creating a new object is usually a bug. At the very least, it is error-prone. Be careful with Object.assign().

Destructuring

const [t, u] = ['a', 'b'];
t; // 'a'
u; // 'b'
const blep = {
blop: 'blop'
};

// The following is equivalent to:
// const blop = blep.blop;
const { blop } = blep;
blop; // 'blop'

As with the array example above, you can destructure to multiple assignments at once. Here’s a line you’ll see in lots of Redux projects:

const { type, payload } = action;

Here’s how it’s used in the context of a reducer (much more on that topic coming later):

const myReducer = (state = {}, action = {}) => {
const { type, payload } = action;
switch (type) {
case 'FOO': return Object.assign({}, state, payload);
default: return state;
}
};

If you don’t want to use a different name for the new binding, you can assign a new name:

const { blop: bloop } = blep;
bloop; // 'blop'

Read: Assign blep.blop as bloop.

Comparisons and Ternaries

3 + 1 === 4; // true

There’s also a sloppy equality operator. It’s formally known as the “Equal” operator. Informally, “double equals”. Double equals has a valid use-case or two, but it’s almost always better to default to the === operator, instead.

Other comparison operators include:

  • > Greater than
  • < Less than
  • >= Greater than or equal to
  • <= Less than or equal to
  • != Not equal
  • !== Not strict equal
  • && Logical and
  • || Logical or

A ternary expression is an expression that lets you ask a question using a comparator, and evaluates to a different answer depending on whether or not the expression is truthy:

14 - 7 === 7 ? 'Yep!' : 'Nope.'; // Yep!

Functions

const double = x => x * 2;

This means the same thing as the mathematical function f(x) = 2x. Spoken out loud, that function reads f of x equals 2x. This function is only interesting when you apply it to a specific value of x. To use the function in other equations, you'd write f(2), which has the same meaning as 4.

In other words, f(2) = 4. You can think of a math function as a mapping from inputs to outputs. f(x) in this case is a mapping of input values for x to corresponding output values equal to the product of the input value and 2.

In JavaScript, the value of a function expression is the function itself:

double; // [Function: double]

You can see the function definition using the .toString() method:

double.toString(); // 'x => x * 2'

If you want to apply a function to some arguments, you must invoke it with a function call. A function call applies a function to its arguments and evaluates to a return value.

You can invoke a function using <functionName>(argument1, argument2, ...rest). For example, to invoke our double function, just add the parentheses and pass in a value to double:

double(2); // 4

Unlike some functional languages, those parentheses are meaningful. Without them, the function won’t be called:

double 4; // SyntaxError: Unexpected number

Signatures

  1. An optional function name.
  2. A list of parameter types, in parentheses. The parameters may optionally be named.
  3. The type of the return value.

Type signatures don’t need to be specified in JavaScript. The JavaScript engine will figure out the types at runtime. If you provide enough clues, the signature can also be inferred by developer tools such as IDEs (Integrated Development Environment) and Tern.js using data flow analysis.

JavaScript lacks its own function signature notation, so there are a few competing standards: JSDoc has been very popular historically, but it’s awkwardly verbose, and nobody bothers to keep the doc comments up-to-date with the code, so many JS developers have stopped using it.

TypeScript and Flow are currently the big contenders. I’m not sure how to express everything I need in either of those, so I use Rtype, for documentation purposes only. Some people fall back on Haskell’s curry-only Hindley–Milner types. I’d love to see a good notation system standardized for JavaScript, if only for documentation purposes, but I don’t think any of the current solutions are up to the task, at present. For now, squint and do your best to keep up with the weird type signatures which probably look slightly different from whatever you’re using.

functionName(param1: Type, param2: Type) => Type

The signature for double is:

double(x: n) => Number

In spite of the fact that JavaScript doesn’t require signatures to be annotated, knowing what signatures are and what they mean will still be important in order to communicate efficiently about how functions are used, and how functions are composed.

Default Parameter Values

const orZero = (n = 0) => n;

To set a default, simply assign it to the parameter with the = operator in the function signature, as in n = 0, above. When you assign default values in this way, type inference tools such as Tern.js, Flow, or TypeScript can infer the type signature of your function automatically, even if you don't explicitly declare type annotations.

The result is that, with the right plugins installed in your editor or IDE, you’ll be able to see function signatures displayed inline as you’re typing function calls. You’ll also be able to understand how to use a function at a glance based on its call signature. Using default assignments wherever it makes sense can help you write more self-documenting code.

Note: Parameters with defaults don’t count toward the function’s .length property, which will throw off utilities such as autocurry which depend on the .length value. Some curry utilities (such as lodash/curry) allow you to pass a custom arity to work around this limitation if you bump into it.

Named Arguments

const createUser = ({
name = 'Anonymous',
avatarThumbnail = '/avatars/anonymous.png'
}) => ({
name,
avatarThumbnail
});
const george = createUser({
name: 'George',
avatarThumbnail: 'avatars/shades-emoji.png'
});
george;
/*
{
name: 'George',
avatarThumbnail: 'avatars/shades-emoji.png'
}
*/

Rest and Spread

For example, the following function simply discards the first argument and returns the rest as an array:

const aTail = (head, ...tail) => tail;
aTail(1, 2, 3); // [2, 3]

Rest gathers individual elements together into an array. Spread does the opposite: it spreads the elements from an array to individual elements. Consider this:

const shiftToLast = (head, ...tail) => [...tail, head];
shiftToLast(1, 2, 3); // [2, 3, 1]

Arrays in JavaScript have an iterator that gets invoked when the spread operator is used. For each item in the array, the iterator delivers a value. In the expression, [...tail, head], the iterator copies each element in order from the tail array into the new array created by the surrounding literal notation. Since the head is already an individual element, we just plop it onto the end of the array and we're done.

Currying

Curry and partial application can be enabled by returning another function:

const highpass = cutoff => n => n >= cutoff;
const gt4 = highpass(4); // highpass() returns a new function

You don’t have to use arrow functions. JavaScript also has a function keyword. We're using arrow functions because the function keyword is a lot more typing. This is equivalent to the highPass() definition, above:

const highpass = function highpass(cutoff) {
return function (n) {
return n >= cutoff;
};
};

The arrow in JavaScript roughly means “function”. There are some important differences in function behavior depending on which kind of function you use (=> lacks its own this, and can't be used as a constructor), but we'll get to those differences when we get there. For now, when you see x => x, think "a function that takes x and returns x". So you can read const highpass = cutoff => n => n >= cutoff; as:

“highpass is a function which takes cutoff and returns a function which takes n and returns the result of n >= cutoff".

Since highpass() returns a function, you can use it to create a more specialized function:

const gt4 = highpass(4);gt4(6); // true
gt4(3); // false

Autocurry lets you curry functions automatically, for maximal flexibility. Say you have a function add3():

const add3 = curry((a, b, c) => a + b + c);

With autocurry, you can use it in several different ways, and it will return the right thing depending on how many arguments you pass in:

add3(1, 2, 3); // 6
add3(1, 2)(3); // 6
add3(1)(2, 3); // 6
add3(1)(2)(3); // 6

Sorry Haskell fans, JavaScript lacks a built-in autocurry mechanism, but you can import one from Lodash:

$ npm install --save lodash

Then, in your modules:

import curry from 'lodash/curry';

Or, you can use the following magic spell:

// Tiny, recursive autocurry
const curry = (
f, arr = []
) => (...args) => (
a => a.length === f.length ?
f(...a) :
curry(f, a)
)([...arr, ...args]);

Function Composition

f . g

Which translates to this in JavaScript:

f(g(x))

It’s evaluated from the inside out:

  1. x is evaluated
  2. g() is applied to x
  3. f() is applied to the return value of g(x)

For example:

const inc = n => n + 1;
inc(double(2)); // 5

The value 2 is passed into double(), which produces 4. 4 is passed into inc() which evaluates to 5.

You can pass any expression as an argument to a function. The expression will be evaluated before the function is applied:

inc(double(2) * double(2)); // 17

Since double(2) evaluates to 4, you can read that as inc(4 * 4) which evaluates to inc(16) which then evaluates to 17.

Function composition is central to functional programming. We’ll have a lot more on it later.

Arrays

const arr = [1, 2, 3];
arr.map(double); // [2, 4, 6]

In this case, arr is the object, .map() is a property of the object with a function for a value. When you invoke it, the function gets applied to the arguments, as well as a special parameter called this, which gets automatically set when the method is invoked. The this value is how .map() gets access to the contents of the array.

Note that we’re passing the double function as a value into map rather than calling it. That's because map takes a function as an argument and applies it to each item in the array. It returns a new array containing the values returned by double().

Note that the original arr value is unchanged:

arr; // [1, 2, 3]

Method Chaining

const arr = [1, 2, 3];
arr.map(double).map(double); // [4, 8, 12]

A predicate is a function that returns a boolean value (true or false). The .filter() method takes a predicate and returns a new list, selecting only the items that pass the predicate (return true) to be included in the new list:

[2, 4, 6].filter(gt4); // [4, 6]

Frequently, you’ll want to select items from a list, and then map those items to a new list:

[2, 4, 6].filter(gt4).map(double); [8, 12]

Note: Later in this text, you’ll see a more efficient way to select and map at the same time using something called a transducer, but there are other things to explore first.

Conclusion

Buy the Book | Index | < Previous | Next >

Learn More at EricElliottJS.com


Eric Elliott is a distributed systems expert and author of the books, “Composing Software” and “Programming JavaScript Applications”. As co-founder of DevAnywhere.io, he teaches developers the skills they need to work remotely and embrace work/life balance. He builds and advises development teams for crypto projects, and has contributed to software experiences for Adobe Systems,Zumba Fitness, The Wall Street Journal, ESPN, BBC, and top recording artists including Usher, Frank Ocean, Metallica, and many more.

He enjoys a remote lifestyle with the most beautiful woman in the world.

JavaScript Scene

JavaScript, software leadership, software development, and…

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store