Search code examples
javascriptrecursionscopetrampolines

Export Function needs to be set as a pure function


In the below piece of Trampoline code, I will be calling onclick = export(add,5) from my button present in my view. How do I ensure that this call always returns the value of 5 without uncommenting the line where //x=0 in the code below?

var x = 0;

function repeat(operation, num) {
    return function () {
        if (num <= 0) {
            console.log(x);
            // x=0;
            return;
        }
        operation();
        return repeat(operation, --num);
    }
}

function trampoline(fn) {
    while (fn && typeof fn === 'function') {
        fn=  fn();
    }
}

this.export = function (operation, num) {
    trampoline(function () {
        return repeat(operation, num);
    });
}

function add()
{
    ++x;
}

Basically, I wanted a solution where the scope of my variable x will ensure the program always returns the same output when executed n number of times (in other words, I want to set 'export' as a pure function)


Solution

  • It's not really clear to me what you're asking, but there's no reason for you to depend on external state, mutations, or reassignment for this function. ++x and --n are nightmares in the fantasy land of functional programming – you'll want to avoid them in almost ever common pattern.

    // checking typeof === 'function' is not really reliable in functional programming
    // function return values are perfectly good return types
    // use data abstraction to mark tail calls
    const trampoline = {
      bounce: (f, x) =>({ isBounce: true, f, x}),
      run: t => {
        while (t && t.isBounce)
          t = t.f(t.x)
        return t
      }
    }
    
    // local binding, no mutation, just return x + 1
    const add = x => x + 1
    
    // bounced repeat
    const repeatAux = (f,n) => x =>
      n === 0 ? x : trampoline.bounce(repeatAux(f, n - 1), f(x))
    
    // this is the function you export
    // it runs trampolined repeatAux with initial state of 0
    const repeat = (f,n) =>
      trampoline.run(repeatAux(f,n)(0))
    
    // all calls to repeat are pure and do not require external state, mutation, or reassignment
    console.log(repeat(add, 5)) // 5
    console.log(repeat(add, 5)) // 5
    console.log(repeat(add, 7)) // 7
    console.log(repeat(add, 7)) // 7
    
    // repeat 1 million times to show trampoline works
    console.log(repeat(add, 1e6)) // 1000000


    ES5, as requested

    var trampoline = {
      bounce: function (f, x) {
        return ({ isBounce: true, f, x})
      },
      run: function (t) {
        while (t && t.isBounce)
          t = t.f(t.x)
        return t
      }
    }
    
    var add = function (x) { return x + 1 }
    
    var repeatAux = function (f,n) {
      return function (x) {
        return n === 0
          ? x
          : trampoline.bounce(repeatAux(f, n - 1), f(x))
      }
    }
    
    var repeat = function (f,n) {
      return trampoline.run(repeatAux(f,n)(0))
    }
    
    console.log(repeat(add, 5)) // 5
    console.log(repeat(add, 5)) // 5
    console.log(repeat(add, 7)) // 7
    console.log(repeat(add, 7)) // 7
    console.log(repeat(add, 1e6)) // 1000000