1

I want a foldr that's similar to the foldr in Haskell or lisp. My foldr causes stack overflow on large arrays, and probably because large numbers of pending operations on the stack can't be reduced until it hits the base case. How would you optimize my foldr so it works reasonably well for large arrays.

const foldr = (f, acc, [x, ...xs]) => 
  (typeof x === 'undefined') 
  ? acc 
  : f (x, foldr (f, acc, xs))

foldr((x, acc) => x + acc, 0, [...Array(100000).keys()])
AJF
  • 11,767
  • 2
  • 37
  • 64
user12457
  • 191
  • 4

2 Answers2

3

foldr is pretty nearly reduceRight:

const flip = f => (a, b) => f(b, a)

const foldr = (f, acc, arr) =>
  arr.reduceRight(flip(f), acc)

Replace arr.reduceRight with [...arr].reduceRight if you’d like to keep the support for arbitrary iterables that [x, ...xs] unpacking gives you.

const flip = f => (a, b) => f(b, a)

const foldr = (f, acc, arr) =>
  arr.reduceRight(flip(f), acc)

console.log(foldr((x, acc) => x + acc, 0, [...Array(100000).keys()]))
Ry-
  • 218,210
  • 55
  • 464
  • 476
1

The problem is that the default list-like structure that JavaScript uses are mutable arrays (not true c-like arrays, they may internally be implemented as trees) while functional languages like Haskell or Lisp use linked lists. You can get the first element and the rest of linked list without mutation in constant time. If you want to do the same in JavaScript (without mutation), you have to create (allocate) new array to get the rest of the array.

However, the whole foldr can be implemented with internal mutation. The whole function won't do any external mutation:

const foldr = (f, initialValue, arr) => {
  let value = initialValue;

  for (let i = arr.length - 1; i >= 0; i--) {
    value = f(arr[i], value)
  }

  return value;
}
Erik Cupal
  • 2,735
  • 1
  • 19
  • 20