DEV Community 👩‍💻👨‍💻

Discussion on: How map() and reduce() Array method works in JavaScript.

Collapse
peerreynders profile image
peerreynders • Edited on

For me the canonical way of learning about map and reduce is to

  1. use recursion
  2. implement foldl (reduce) using recursion
  3. implement map in terms of foldl

Now in JavaScript this isn't terribly efficient …

const personArray = [
  { name: 'Swastik', age: 23 },
  { name: 'John', age: 42 },
  { name: 'Rock', age: 45 },
];

type Person = typeof personArray[number];

// reduce
function foldl<X, Y>(fn: (y: Y, x: X) => Y, to: Y, from: X[]): Y {
  return from.length < 1 ? to : foldl(fn, fn(to, from[0]), from.slice(1));
}

function map<X, Y>(fn: (x: X) => Y, from: X[]): Y[] {
  const append: (ys: Y[], x: X) => Y[] = (ys, x) => {
    ys.push(fn(x));
    return ys;
  };
  return foldl(append, [], from);
}

function rmap<X, Y>(fn: (x: X) => Y, from: X[]): Y[] {
  // return from.length < 1 ?
  //   [] :
  //   [fn(from[0]), ...rmap(fn, from.slice(1))];
  //
  if (from.length < 1) return [];

  const rest = rmap(fn, from.slice(1));
  rest.unshift(fn(from[0]));
  return rest;
}

function filter<X>(fn: (x: X) => boolean, from: X[]): X[] {
  const appendAccept: (xs: X[], x: X) => X[] = (xs, x) => {
    if (fn(x)) xs.push(x);
    return xs;
  };
  return foldl(appendAccept, [], from);
}

function rfilter<X>(fn: (x: X) => boolean, from: X[]): X[] {
  // return from.length < 1 ?
  //   [] :
  //   fn(from[0]) ?
  //     [from[0], ...rfilter(fn, from.slice(1))] :
  //     rfilter(fn, from.slice(1));
  //
  if (from.length < 1) return [];

  const rest = rfilter(fn, from.slice(1));
  if (fn(from[0])) rest.unshift(from[0]);
  return rest;
}

type People = {
  [name: string]: number;
};

const appendPerson: (a: People, p: Person) => People = (people, person) => {
  people[person.name] = person.age;
  return people;
};

console.log(foldl(appendPerson, {}, personArray));

const extractName: (p: Person) => string = (person) => person.name;

console.log(map(extractName, personArray));
console.log(rmap(extractName, personArray));

const inUpperAlphabet: (p: Person) => boolean = (person) =>
  person.name[0].toLowerCase() > 'm';

console.log(filter(inUpperAlphabet, personArray));
console.log(rfilter(inUpperAlphabet, personArray));
Enter fullscreen mode Exit fullscreen mode

This transpiles down to

const personArray = [
  { name: 'Swastik', age: 23 },
  { name: 'John', age: 42 },
  { name: 'Rock', age: 45 },
];
// reduce
function foldl(fn, to, from) {
  return from.length < 1 ? to : foldl(fn, fn(to, from[0]), from.slice(1));
}
function map(fn, from) {
  const append = (ys, x) => {
    ys.push(fn(x));
    return ys;
  };
  return foldl(append, [], from);
}
function rmap(fn, from) {
  if (from.length < 1) return [];
  const rest = rmap(fn, from.slice(1));
  rest.unshift(fn(from[0]));
  return rest;
}
function filter(fn, from) {
  const appendAccept = (xs, x) => {
    if (fn(x)) xs.push(x);
    return xs;
  };
  return foldl(appendAccept, [], from);
}
function rfilter(fn, from) {
  if (from.length < 1) return [];
  const rest = rfilter(fn, from.slice(1));
  if (fn(from[0])) rest.unshift(from[0]);
  return rest;
}
const appendPerson = (people, person) => {
  people[person.name] = person.age;
  return people;
};
console.log(foldl(appendPerson, {}, personArray));
const extractName = (person) => person.name;
console.log(map(extractName, personArray));
console.log(rmap(extractName, personArray));
const inUpperAlphabet = (person) => person.name[0].toLowerCase() > 'm';
console.log(filter(inUpperAlphabet, personArray));
console.log(rfilter(inUpperAlphabet, personArray));
Enter fullscreen mode Exit fullscreen mode