如何正确序列化 Javascript curried 箭头函数?
How to correctly serialize Javascript curried arrow functions?
const makeIncrementer = s=>a=>a+s
makeIncrementer(10).toString() // Prints 'a=>a+s'
这将导致无法正确反序列化(我希望使用 a=>a+10
之类的东西。
有什么办法可以做到吗?
这是一个很好的问题。虽然我没有完美的答案,但您可以获得有关 argument/s 的详细信息的一种方法是创建一个构建器函数来为您存储必要的详细信息。不幸的是,我想不出一种方法来了解哪些内部变量与哪些值相关。如果我发现任何其他问题,我会更新:
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
更新:这将是一项艰巨的任务,但我意识到,如果你扩展上面的构建器想法,你可以 write/use 一个函数字符串解析器,它可以接受给定的参数,以及外部函数,并将日志重写为序列化版本。我在下面有一个演示,但它在实际用例中不起作用!。我已经完成了一个简单的字符串 find/replace,而您将需要使用实际的函数解析器来正确替换。这只是您如何做到这一点的一个例子。 请注意,我还使用了两个增量变量只是为了展示如何做倍数。
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
您不应该 serialize/parse 函数体,因为这会很快导致安全漏洞。序列化一个闭包意味着序列化它的本地状态,也就是说你必须让闭包的自由变量对周围的范围可见:
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
);
我使用 Symbol
是为了新的 属性 不会干扰您程序的其他部分。
如代码中所述,您仍然无法序列化高阶函数。
结合目前两个答案的想法,我设法产生了一些有用的东西(虽然我还没有彻底测试它):
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 的宝贵意见!
const makeIncrementer = s=>a=>a+s
makeIncrementer(10).toString() // Prints 'a=>a+s'
这将导致无法正确反序列化(我希望使用 a=>a+10
之类的东西。
有什么办法可以做到吗?
这是一个很好的问题。虽然我没有完美的答案,但您可以获得有关 argument/s 的详细信息的一种方法是创建一个构建器函数来为您存储必要的详细信息。不幸的是,我想不出一种方法来了解哪些内部变量与哪些值相关。如果我发现任何其他问题,我会更新:
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
更新:这将是一项艰巨的任务,但我意识到,如果你扩展上面的构建器想法,你可以 write/use 一个函数字符串解析器,它可以接受给定的参数,以及外部函数,并将日志重写为序列化版本。我在下面有一个演示,但它在实际用例中不起作用!。我已经完成了一个简单的字符串 find/replace,而您将需要使用实际的函数解析器来正确替换。这只是您如何做到这一点的一个例子。 请注意,我还使用了两个增量变量只是为了展示如何做倍数。
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
您不应该 serialize/parse 函数体,因为这会很快导致安全漏洞。序列化一个闭包意味着序列化它的本地状态,也就是说你必须让闭包的自由变量对周围的范围可见:
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
);
我使用 Symbol
是为了新的 属性 不会干扰您程序的其他部分。
如代码中所述,您仍然无法序列化高阶函数。
结合目前两个答案的想法,我设法产生了一些有用的东西(虽然我还没有彻底测试它):
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 的宝贵意见!