Search code examples
javascriptdependency-injectionparameter-passingevalwrapper

Wrapper for javascript map function


I have two javascript functions stack(y, f) and val(yi, x).

stack(y, f) should behave like y.map(function(yi) { f } )

it returns an array r, where each element r[i] is a function of the corresponding element of an array parameter y[i] and an expression f passed as second parameter.

f can be any javascript code that evaluates to a single value, however, if it depends on function val, this function would depend on y[i].

[r[0], r[1], ...] = stack( [y[0], y[1], ...], foo( val(y[i], x1), val(y[i], x2), ... ) )

I considered the following ugly approach, where I pass the expression as string, and in a loop string-replace - then evaluate it. https://jsfiddle.net/zuyt954g/

function val(yi,x) {
    return 10**yi+x;
}

function foo(x1,x2,x3) {
    return x1+x2+x3;
}

function stack(y,f) {
    return y.map(function(yi) {
        return eval(f.replace(/yi/g, yi));
    });
}

console.log([1,2].map(function(yi) {
  return foo(val(yi,3),val(yi,4),val(yi,5))
} ))
>> [42, 312]

console.log(stack([1,2], "foo(val(yi,3),val(yi,4),val(yi,5))"))
>> [42, 312]

I wonder whether you could see any cleaner solution, where ideally I would avoid evaluating a string expression. As an alternative I thought of already val returning an array, if it detects stack as one of its distant parents, but getting the call stack and y seems non-trivial.


Solution

  • any cleaner solution, where ideally I would avoid evaluating a string expression

    Simple, just use a function instead. You get rid of the very error prone strings, you make your code more maintainable, and you make it easier to read and understand.

    The string "foo(val(yi,3),val(yi,4),val(yi,5))" can be transformed into the function:

    function(yi) {
        return foo(val(yi,3),val(yi,4),val(yi,5));
    }
    

    and when called instead of replacing the string yi, now you can just pass it in, so

    eval(f.replace(/yi/g, yi));
    

    transforms into simply

    f(yi);
    

    Here is how this transformation looks:

    function val(yi, x) {
      return 10 ** yi + x;
    }
    
    function foo(x1, x2, x3) {
      return x1 + x2 + x3;
    }
    
    function stack(y, f) {
      return y.map(function(yi) {
        return f(yi);
      })
    }
    
    console.log([1, 2].map(function(yi) {
      return foo(val(yi, 3), val(yi, 4), val(yi, 5))
    }))
    //>> [42, 312]
    
    console.log(stack([1, 2], function(yi) {
      return foo(val(yi, 3), val(yi, 4), val(yi, 5))
    }))
    //>> [42, 312]

    However, now we can improve it a bit. There is a bit of redundancy here:

    return y.map(function(yi) {
      return f(yi);
    })
    

    map receives a callback, that takes one parameter and calls another function that also takes the same parameter. Both functions have arity of 1 the only purpose of the anonymous callback is to forward the parameter to f. Therefore, calling the anonymous callback with yi is the same as calling f(yi). We can remove this "empty wrapper" by what in Lambda Calculus is called Eta reduction since f does exactly the same as the callback. In our case this means that stack becomes:

    function stack(y, f) {
      return y.map(f)
    }
    

    As a final note, we can further prove the equality by just re-using the function

    function(yi) {
      return foo(val(yi, 3), val(yi, 4), val(yi, 5))
    }
    

    which now exists twice:

    function val(yi, x) {
      return 10 ** yi + x;
    }
    
    function foo(x1, x2, x3) {
      return x1 + x2 + x3;
    }
    
    function stack(y, f) {
      return y.map(f);
    }
    
    function testFunction(yi) {                        // ---|
      return foo(val(yi, 3), val(yi, 4), val(yi, 5));  //    |->-
    }                                                  // ---|  |
    //                                                          v
    //                                                          |
    console.log([1, 2].map(testFunction)) //<-----------------<--
    //>> [42, 312]                                              |
    //                                                          v
    //                                                          |
    console.log(stack([1, 2], testFunction)) //<--------------<--
    //>> [42, 312]