Skip to content
wannacode

Arrow functions

Problem

A whole function for one comparison

The orders need to be sorted by total.

Order
class Order {
constructor(customer, total) {
this.customer = customer;
this.total = total;
}
}
const orders = [
new Order("Maria", 75.5),
new Order("Georgi", 12.9),
new Order("Elena", 40),
];
function byTotal(a, b) {
return a.total - b.total;
}
orders.sort(byTotal);
for (const o of orders) {
console.log(`${o.customer}: €${o.total.toFixed(2)}`);
}
Output
Georgi: €12.90
Elena: €40.00
Maria: €75.50

sort takes a compare function. It gets two orders and returns a negative number if the first should come before the second, a positive number if it comes after it, and 0 if they are equal. a.total - b.total does exactly that.

It works. But for one comparison you write a whole function with a name, far from the place where you use it. And it is called only here. You want to pass just the rule.

The idea

An arrow function: just the rule

The same sort, on one line:

orders.sort((a, b) => a.total - b.total);

(a, b) => a.total - b.total is called an arrow function. In other languages the same thing is called a lambda. To the left of => are the parameters, to the right is the body. This is the function byTotal with no name, no function and no return.

The ways to write it:

  • One parameter can go without parentheses: o => o.total > 50. Usually you write the parentheses: (o) => .... With no parameters it is () => ....
  • A body with several lines goes in { }, and then return is required.
const byTotal = (a, b) => a.total - b.total;
const byTotalLong = (a, b) => {
const diff = a.total - b.total;
return diff < 0 ? -1 : diff > 0 ? 1 : 0;
};
const guest = () => new Order("Guest", 0);
orders.sort(byTotalLong);
for (const o of orders) {
console.log(`${o.customer}: €${o.total.toFixed(2)}`);
}
console.log(guest().customer);
Output
Georgi: €12.90
Elena: €40.00
Maria: €75.50
Guest

An arrow function is a value: you can put it in a variable and pass it later.

More precisely, if you are curious

If the body is an object, it goes in parentheses. Without them, { is read as the start of a body, and the function returns undefined:

const guest = () => ({ customer: "Guest" });
const broken = () => { customer: "Guest" };
console.log(guest().customer);
console.log(broken());
Output
Guest
undefined

The idea

A function is a value

How does sort know that (a, b) => ... is the compare function? It does not need to know anything more. In JavaScript a function is a value, like a number or a string: you can put it in a variable, pass it to another function and call it with parentheses.

const student = (price) => price * 0.8;
const minusFive = (price) => price - 5;
console.log(student(50));
console.log(minusFive(50));
Output
40
45

student is an object of type function: typeof student is "function". You call it like any function: student(50). Two price rules, and not a single class.

Here Java needs a functional interface: an interface with one method that says what kind of rule it is. JavaScript does not write types and has no interfaces, so it does not need anything like that. Any function can be passed and called.

const student = (price) => price * 0.8;
const minusFive = (price) => price - 5;
console.log(student(50));
console.log(minusFive(50));
Step by step, in words
  1. The arrow function is an object of type function, and student points to it. Its body does not run yet.
  2. A second rule, a second object. Not a single class is written.
  3. student(50) passes 50 to the function: price is 50, and price * 0.8 gives 40.
  4. minusFive(50): the same number, a different rule, 45.
More precisely, if you are curious

An arrow function takes this from the place where it is written, like a lambda in Java. A function written with function has its own this, and when forEach calls it, that is undefined:

class Cart {
constructor(owner) {
this.owner = owner;
this.items = ["coffee", "honey"];
}
print() {
this.items.forEach((item) => console.log(`${this.owner}: ${item}`));
}
printOld() {
this.items.forEach(function (item) {
console.log(`${this.owner}: ${item}`);
});
}
}
const cart = new Cart("Maria");
cart.print();
cart.printOld();
Output
Maria: coffee
Maria: honey
TypeError: Cannot read properties of undefined (reading 'owner')

So write a rule inside a class method as an arrow function.

The idea

The four kinds of rules

The type of every rule is function, but there are four kinds of rules. It helps to recognize them, because the functions that take rules want a specific kind:

  • condition: takes a value, returns true or false. "Is the order big?"
  • transformation: takes one thing, returns another. An order into a label.
  • action: takes a value and does something with it: prints it, saves it. Its result is not used.
  • supplier: takes nothing, returns a new object when you ask.
const big = (o) => o.total >= 50;
const label = (o) => o.customer.toUpperCase();
const show = (text) => console.log("-> " + text);
const guest = () => new Order("Guest", 0);
const order = new Order("Maria", 75.5);
console.log(big(order));
show(label(order));
console.log(guest().customer);
Output
true
-> MARIA
Guest

In Java, rules are objects with methods that combine them: conditions are joined with and, or and negate ("not"), and functions are chained one after another with andThen. JavaScript has no such methods. The new rule is a new function that calls the old ones: (o) => big(o) && maria(o). Two functions one after another are also combined with a function:

const withVat = (price) => price * 1.2;
const rounded = (price) => Math.round(price * 100) / 100;
const andThen = (f, g) => (x) => g(f(x));
const finalPrice = andThen(withVat, rounded);
console.log(finalPrice(10.555));
Output
12.67

andThen takes two functions and returns a third one: it calls the first, and passes its result to the second.

More precisely, if you are curious

An action still returns something: undefined, because that is the result of console.log. console.log(show("coffee")) prints "-> coffee" and then undefined.

Try it

Combined conditions

What will the program print?

const big = (o) => o.total >= 50;
const maria = (o) => o.customer === "Maria";
const bigAndMaria = (o) => big(o) && maria(o);
const bigOrMaria = (o) => big(o) || maria(o);
const notBig = (o) => !big(o);
const small = new Order("Maria", 30);
const large = new Order("Georgi", 80);
console.log(bigAndMaria(small));
console.log(bigOrMaria(small));
console.log(notBig(large));
Answer

See it

Arrays take functions

Arrays have methods that take a rule instead of a loop:
let cart = ["bread", "", "milk", " ", "coffee"];
cart = cart.filter((item) => item.trim() !== "");
cart.forEach((item) => console.log(item));
const customers = [
{ name: "Maria", city: "Plovdiv" },
{ name: "Georgi", city: "Sofia" },
{ name: "Elena", city: "Plovdiv" },
];
const byCity = Map.groupBy(customers, (c) => c.city);
byCity.forEach((list, city) => console.log(`${city}: ${list.map((c) => c.name).join(", ")}`));
Output
bread
milk
coffee
Plovdiv: Maria, Elena
Sofia: Georgi
  • filter(condition) makes a new array with only the elements for which the condition is true. The old one does not change: here cart is let and just points to the new one. There is no method that removes elements from the array itself by a condition.
  • forEach(action) does the action with each element.
  • Map.groupBy(array, function) groups: the function says the key of each element, and the result is a Map with an array of the elements for each key.
  • The forEach of a Map takes a function with two parameters, but in reverse order: the value first, then the key.

{ name: "Maria", city: "Plovdiv" } is an object without a class: just fields. A Map keeps the keys in the order they were added, which is why Plovdiv comes before Sofia.

The idea

A function by name, without parentheses

Often an arrow function only passes its parameter to an existing function: (s) => Number(s). Then you can pass the function itself, by name and without parentheses:
const names = ["maria", "georgi"];
names.forEach((name) => console.log(name));
const upper = (s) => s.toUpperCase();
const parse = Number;
console.log(upper("elena"));
console.log(parse("42") + 1);
console.log(["1", "2", "3"].map(Number));
console.log(["1", "2", "3"].map(parseInt));
Output
maria
georgi
ELENA
43
[ 1, 2, 3 ]
[ 1, NaN, NaN ]

Java has a separate notation for this, the method reference with ::. In JavaScript the name is enough, with one catch: the function gets everything the caller passes to it. map and forEach pass the element, its index and the whole array. Number looks only at the first one and works. parseInt takes a second parameter, the radix (the base of the number system), and gets the element's index: parseInt("2", 1) is NaN. The same thing happens with console.log, which prints everything it gets:

["maria", "georgi"].forEach(console.log);
Output
maria 0 [ 'maria', 'georgi' ]
georgi 1 [ 'maria', 'georgi' ]

A method of the parameter, like toUpperCase, has no short form. String.prototype.toUpperCase cannot be passed directly: the program stops with a TypeError. You write (s) => s.toUpperCase().

So pass by name only functions whose parameters you know. Otherwise write an arrow function.

See it

Sorting by several fields

The compare function can check several fields one after another. If the first comparison gives 0, || moves on to the next one: 0 is "falsy" in JavaScript.

The students are sorted by grade, from the highest, and by name when the grades are equal:

const students = [
new Student("Ivan", 5.5),
new Student("Ana", 6),
new Student("Peter", 5.5),
new Student("Boris", 4.75),
];
students.sort((a, b) => b.grade - a.grade || a.name.localeCompare(b.name));
Output
Ana 6
Ivan 5.5
Peter 5.5
Boris 4.75

b.grade - a.grade puts the higher grade first. When the grades are equal, a.name.localeCompare(b.name) compares the names alphabetically. You read it from left to right: "by grade in reverse, and by name when they are equal".

More precisely, if you are curious
  • Without a function, sort turns the elements into strings and compares the strings. For numbers this gives the wrong order:
console.log([10, 9, 1].sort());
console.log([10, 9, 1].sort((a, b) => a - b));
const prices = [40, 12, 75];
const sorted = prices.toSorted((a, b) => a - b);
console.log(sorted, prices);
Output
[ 1, 10, 9 ]
[ 1, 9, 10 ]
[ 12, 40, 75 ] [ 40, 12, 75 ]
  • sort sorts the array itself. toSorted returns a new sorted array, and the old one stays as it is.

Try it

What does reversed reverse?

Here reversed takes a compare function and returns a new one that swaps a and b.

What will the program print?

const students = [
new Student("Ivan", 5.5),
new Student("Ana", 6),
new Student("Peter", 5.5),
new Student("Boris", 4.75),
];
const byGradeThenName = (a, b) => a.grade - b.grade || a.name.localeCompare(b.name);
const reversed = (compare) => (a, b) => compare(b, a);
students.sort(reversed(byGradeThenName));
students.forEach((s) => console.log(`${s.name} ${s.grade}`));
Answer

Try it

Find the bug

The program should print the sum of the prices. It stops with an error at the very first price. Which line is the cause on?

Pick the line with the bug.

More precisely, if you are curious

An arrow function sees the variable itself, not a copy. If the variable changes after the arrow function, the next call sees the new value:

let rate = 0.2;
const discount = (price) => price * (1 - rate);
console.log(discount(100));
rate = 0.5;
console.log(discount(100));
Output
80
50

In Java this is forbidden precisely because of surprises like this. If the rule needs to remember a value, pass it as a parameter.

Exercise

Rules as values

Solve the tasks in your editor.

1. Sorting by several fields. A class Product with a name, a category and a price. Sort the products by category, within a category from the most expensive, and by name when the price is equal, with one sort and a compare function with ||. The products are: mouse, accessories, €25; laptop, computers, €1299; keyboard, accessories, €49.90; tablet, computers, €399; headphones, accessories, €49.90.

Output
Accessories | Headphones | €49.90
Accessories | Keyboard | €49.90
Accessories | Mouse | €25.00
Computers | Laptop | €1299.00
Computers | Tablet | €399.00

2. Filtering orders. Add a city to Order. Write a function select(orders, rule) that returns a new list with only the orders for which the rule is true. Make two rules, sofia and big (at least €50), and print the orders for which both are true, the ones outside Sofia and the ones "in Sofia or at least €100". Each of the three rules is an arrow function that calls sofia and big. The orders: Maria, Sofia, €75.50; Georgi, Varna, €12.90; Elena, Sofia, €40; Dimitar, Plovdiv, €120.

Output
Sofia, over €50:
Maria, Sofia: €75.50
Outside Sofia:
Georgi, Varna: €12.90
Dimitar, Plovdiv: €120.00
Free delivery (Sofia or over €100):
Maria, Sofia: €75.50
Elena, Sofia: €40.00
Dimitar, Plovdiv: €120.00
3. Discounts one after another. Make three discounts as arrow functions: a student discount (−10%), a Black Friday discount (−€20) and "not below zero". Write andThen(f, g), which returns a function: f first, then g. Use it to chain the discounts in two orders: the student discount first and the Friday discount first. Try it with €120 and with €15. Does the order matter?
Output
€120.00 -> student, then Friday: €88.00; Friday, then student: €90.00
€15.00 -> student, then Friday: €0.00; Friday, then student: €0.00

More tasks

  • Cleaning a list. From an array of emails, remove the empty ones and the ones without @ with one filter, then print the rest with forEach.
  • Students by class. Group students by class ("10A", "10B") with Map.groupBy and print each class with the names in it.
  • Validators. Make an object with three validators as arrow functions: non-empty text, digits only (/^\d+$/.test(value)), length up to 10. Try each one on a few strings.

Test

Check yourself

1. Which one is an arrow function that returns the sum of two prices?
2. How do you build the condition "big and from Maria" out of the functions big and maria?
3. What does ["1", "2", "3"].map(parseInt) return?
4. How does students.sort((a, b) => b.grade - a.grade || a.name.localeCompare(b.name)) sort?
5. const total = 0; prices.forEach((p) => (total += p)); What happens?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·