简体   繁体   English

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

[英]How to correctly serialize Javascript curried arrow functions?

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

which would make it impossible to de-serialize correctly (I would expect something like a=>a+10 instead. Is there a way to do it right? 这将使得无法正确反序列化(我会期待类似a=>a+10东西。有没有办法做正确的?

This is a great question. 这是一个很好的问题。 While I don't have a perfect answer, one way you could get details about the argument/s is to create a builder function that stores the necessary details for you. 虽然我没有完美的答案,但您可以通过一种方式获得有关参数的详细信息,即创建一个构建函数,为您存储必要的详细信息。 Unfortunately I can't figure out a way to know which internal variables relate to which values. 不幸的是,我无法找到一种方法来了解哪些内部变量与哪些值相关。 If I figure out anything else i'll update: 如果我弄明白我还会更新其他内容:

 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 


UPDATE: It will be a mammoth task, but I realised, that if you expand on the builder idea above, you could write/use a function string parser, that could take the given args, and the outer function, and rewrite the log to a serialised version. 更新:这将是一个庞大的任务,但我意识到,如果你扩展上面的构建器的想法,你可以编写/使用一个函数字符串解析器,可以采取给定的args和外部函数,并重写日志到序列化版本。 I have a demo below, but it will not work in real use cases! 我在下面有一个演示, 但它不适用于实际用例! . I have done a simple string find/replace, while you will need to use an actual function parser to replace correctly. 我已经完成了一个简单的字符串查找/替换,而您需要使用实际的函数解析器来正确替换。 This is just an example of how you could do it. 这只是你如何做到这一点的一个例子。 Note that I also used two incrementer variables just to show how to do multiples. 请注意,我还使用了两个增量变量来展示如何进行倍数。

 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 

You shouldn't serialize/parse function bodies since this quickly leads to security vulnerabilities. 您不应该序列化/解析函数体,因为这很快就会导致安全漏洞。 Serializing a closure means to serialize its local state, that is you have to make the closure's free variables visible for the surrounding scope: 序列化闭包意味着序列化其本地状态,即必须使闭包的自由变量对于周围的作用域可见:

 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 ); 

I use a Symbol in order that the new property doesn't interfere with other parts of your program. 我使用Symbol来使新属性不会干扰程序的其他部分。

As mentioned in the code you still cannot serialize higher order functions. 如代码中所述,您仍然无法序列化高阶函数。

Combining ideas from the two answers so far, I managed to produce something that works (though I haven't tested it thoroughly): 到目前为止,结合两个答案的想法,我设法生产一些有用的东西(虽然我还没有彻底测试过):

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]

This works for arrow functions, that do not have default initializers for the arguments. 这适用于箭头函数,它没有参数的默认初始值设定项。 It supports higher order functions as well. 它也支持更高阶的功能。 One still has to be able to wrap the original function into serializable before applying it to any arguments though. 在将原始函数应用于任何参数之前,仍然必须能够将原始函数包装成可serializable Thank you @MattWay and @ftor for valuable input ! 感谢@MattWay和@ftor的宝贵意见!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM