Item 26: Use Functional Constructs and Libraries to Help Types Flow
May 10, 2024 ยท View on GitHub
Things to Remember
- Use built-in functional constructs and those in utility libraries like Lodash instead of hand-rolled constructs to improve type flow, increase legibility, and reduce the need for explicit type annotations.
Code Samples
const csvData = "...";
const rawRows = csvData.split('\n');
const headers = rawRows[0].split(',');
const rows = rawRows.slice(1).map((rowStr) => {
const row = {};
rowStr.split(",").forEach((val, j) => {
row[headers[j]] = val;
});
return row;
});
import _ from 'lodash';
const rows = rawRows.slice(1)
.map(rowStr => _.zipObject(headers, rowStr.split(',')));
const rowsImperative = rawRows.slice(1).map(rowStr => {
const row = {};
rowStr.split(',').forEach((val, j) => {
row[headers[j]] = val;
// ~~~~~~~~~~~~ No index signature with a parameter of
// type 'string' was found on type '{}'
});
return row;
});
const rowsFunctional = rawRows.slice(1)
.map((rowStr) =>
rowStr
.split(",")
.reduce(
(row, val, i) => ((row[headers[i]] = val), row),
// ~~~~~~~~~~~~~~~ No index signature with a parameter of
// type 'string' was found on type '{}'
{}
)
);
const rowsLodash =
rawRows.slice(1).map(rowStr => _.zipObject(headers, rowStr.split(',')));
rowsLodash
// ^? const rowsLodash: _.Dictionary<string>[]
interface BasketballPlayer {
name: string;
team: string;
salary: number;
}
declare const rosters: {[team: string]: BasketballPlayer[]};
let allPlayers = [];
// ~~~~~~~~~~ Variable 'allPlayers' implicitly has type 'any[]'
// in some locations where its type cannot be determined
for (const players of Object.values(rosters)) {
allPlayers = allPlayers.concat(players);
// ~~~~~~~~~~ Variable 'allPlayers' implicitly has an 'any[]' type
}
let allPlayers: BasketballPlayer[] = [];
for (const players of Object.values(rosters)) {
allPlayers = allPlayers.concat(players); // OK
}
const allPlayers = Object.values(rosters).flat(); // OK
// ^? const allPlayers: BasketballPlayer[]
const teamToPlayers: {[team: string]: BasketballPlayer[]} = {};
for (const player of allPlayers) {
const {team} = player;
teamToPlayers[team] = teamToPlayers[team] || [];
teamToPlayers[team].push(player);
}
for (const players of Object.values(teamToPlayers)) {
players.sort((a, b) => b.salary - a.salary);
}
const bestPaid = Object.values(teamToPlayers).map(players => players[0]);
bestPaid.sort((playerA, playerB) => playerB.salary - playerA.salary);
console.log(bestPaid);
const bestPaid = _(allPlayers)
.groupBy(player => player.team)
.mapValues(players => _.maxBy(players, p => p.salary)!)
.values()
.sortBy(p => -p.salary)
.value();
console.log(bestPaid.slice(0, 10));
// ^? const bestPaid: BasketballPlayer[]