如何正确序列化Javascript curried arrow函数?

时间:2018-04-10 01:18:44

标签: javascript serialization closures currying arrow-functions

const makeIncrementer = s=>a=>a+s
makeIncrementer(10).toString()    // Prints 'a=>a+s'

这使得无法正确反序列化(我希望改为a=>a+10。 有没有办法做得对?

3 个答案:

答案 0 :(得分:4)

这是一个很好的问题。虽然我没有完美的答案,但您可以通过一种方式获得有关参数的详细信息,即创建一个构建函数,为您存储必要的详细信息。不幸的是,我无法找到一种方法来了解哪些内部变量与哪些值相关。如果我弄明白我还会更新其他内容:

const makeIncrementer = s => a => a + s
const builder = (fn, ...args) => {
  return {
    args,
    curry: fn(...args)
  }  
}
var inc = builder(makeIncrementer, 10)
console.log(inc) // logs args and function details
console.log(inc.curry(5)) // 15

更新:这将是一个庞大的任务,但我意识到,如果你扩展上面的构建器想法,你可以编写/使用一个函数字符串解析器,它可以采用给定的args和外部函数,并重写日志到序列化版本。我在下面有一个演示,但它在实际用例中不起作用!。我已经完成了一个简单的字符串查找/替换,而您需要使用实际的函数解析器来正确替换。这只是你如何做到这一点的一个例子。 请注意,我还使用了两个增量变量来展示如何进行倍数。

function replaceAll(str, find, replace) {
  return str.replace(new RegExp(find, 'g'), replace)
}

const makeIncrementer = (a, b) => c => c + a + b
const builder = (fn, ...args) => {
  // get the outer function argument list
  var outers = fn.toString().split('=>')[0]
  // remove potential brackets and spaces
  outers = outers.replace(/\(|\)/g,'').split(',').map(i => i.trim())
  // relate the args to the values
  var relations = outers.map((name, i) => ({ name, value: args[i] }))
  // create the curry
  var curry = fn(...args)
  // attempt to replace the string rep variables with their true values
  // NOTE: **this is a simplistic example and will break easily**
  var serialised = curry.toString()
  relations.forEach(r => serialised = replaceAll(serialised, r.name, r.value))
  return {
    relations,
    serialised,
    curry: fn(...args)
  }  
}
var inc = builder(makeIncrementer, 10, 5)
console.log(inc) // shows args, serialised function, and curry
console.log(inc.curry(4)) // 19

答案 1 :(得分:2)

您不应该序列化/解析函数体,因为这很快就会导致安全漏洞。序列化闭包意味着序列化其本地状态,即你必须使闭包的自由变量对于周围的范围可见:



PUBLIC_URL




我使用const RetrieveArgs = Symbol(); const metaApply = f => x => { const r = f(x); if (typeof r === "function") { if (f[RetrieveArgs]) r[RetrieveArgs] = Object.assign({}, f[RetrieveArgs], {x}); else r[RetrieveArgs] = {x}; } return r; } const add = m => n => m + n, f = metaApply(add) (10); console.log( JSON.stringify(f[RetrieveArgs]) // {"x":10} ); const map = f => xs => xs.map(f) g = metaApply(map) (n => n + 1); console.log( JSON.stringify(g[RetrieveArgs]) // doesn't work with higher order functions );,以便新属性不会干扰您程序的其他部分。

如代码中所述,您仍无法序列化高阶函数。

答案 2 :(得分:1)

到目前为止,结合两个答案的想法,我设法制作了一些有效的东西(虽然我没有彻底测试过):

const removeParentheses = s => {
    let match = /^\((.*)\)$/.exec(s.trim());
    return match ? match[1] : s;
}

function serializable(fn, boundArgs = {}) {
    if (typeof fn !== 'function') return fn;
    if (fn.toJSON !== undefined) return fn;

    const definition = fn.toString();
    const argNames = removeParentheses(definition.split('=>', 1)[0]).split(',').map(s => s.trim());

    let wrapper = (...args) => {
        const r = fn(...args);

        if (typeof r === "function") {
            let boundArgsFor_r = Object.assign({}, boundArgs);
            argNames.forEach((name, i) => {
                boundArgsFor_r[name] = serializable(args[i]);
            });
            return serializable(r, boundArgsFor_r);
        }
        return r;
    }

    wrapper.toJSON = function () {
        return { function: { body: definition, bound: boundArgs } };
    }
    return wrapper;
}

const add = m => m1 => n => m + n * m1,
    fn = serializable(add)(10)(20);

let ser1, ser2;

console.log(
    ser1 = JSON.stringify(fn)          // {"function":{"body":"n => m + n * m1","bound":{"m":10,"m1":20}}}
);

const map = fn => xs => xs.map(fn),
    g = serializable(map)(n => n + 1);

console.log(
    ser2 = JSON.stringify(g)   // {"function":{"body":"xs => xs.map(fn)","bound":{"fn":{"function":{"body":"n => n + 1","bound":{}}}}}}
);

const reviver = (key, value) => {
    if (typeof value === 'object' && 'function' in value) {
        const f = value.function;
        return eval(`({${Object.keys(f.bound).join(',')}}) => (${f.body})`)(f.bound);
    }
    return value;
}

const rev1 = JSON.parse(ser1, reviver);
console.log(rev1(5));   // 110

const rev2 = JSON.parse(ser2, reviver);
console.log(rev2([1, 2, 3]));   // [2, 3, 4]

这适用于箭头函数,它们没有参数的默认初始值设定项。它也支持更高阶的功能。 一个人仍然必须能够将原始函数包装到serializable 之前将其应用于任何参数。 感谢@MattWay和@ftor的宝贵意见!

相关问题