是否可以使用javascript确定javascript函数是否为" pure"?
当一个函数以可预测的方式运行时,它被称为纯,在某种意义上,对于每个x,函数将始终返回相同的关联y值(即单值映射) )。
例如,一个纯函数:
function pure(x) {
return x * x;
}
并且不纯洁:
var x = 0;
function impure(y) {
x = x + y;
return x++;
}
虽然在impure(0) !== impure(0)
这里很容易说出来,但对于以下功能来说,它并不明显:
function weird(x) {
if (x === "specificThing") {
return false;
} else {
return true;
}
}
或
var count = 0;
function surprise(x) {
count++;
if (count === 10e10 && x === 0) {
return true;
} else {
return false;
}
}
另一种问这个问题的方法是,是否可以使用javascript确定javascript函数是否为" impure"?
理论上可能有可能或不可能,但实际上可以采取哪些步骤来开始确定这一点,可能会给出一组约束或假设?
纯度的另一个定义包括可能不会改变非局部变量的警告,但我想考虑一个单独的问题。在这种情况下,我们正在考虑将一致输入映射到一致输出的函数。
答案 0 :(得分:3)
JavaScript是Turing complete,因此它可以像解析任何其他编程语言一样分析和分析JavaScript。所以,问题实际上是:" JavaScript功能是否可以自动测试"纯度","
简短回答
有时候。
答案很长
对于某些函数,AST是直截了当且所有符号都包含在内。像function(X) { return X * X; }
这样的东西可以证明是纯粹的(用于原始输入),因为函数体中使用的唯一变量是作为函数参数传入的变量。此函数不依赖于任何其他API调用,而是纯粹的算术。我们绝对可以证明它是纯粹的。
当我们允许任意内容时,事情会发生变化,因为当JavaScript需要执行可以&的操作时,JavaScript没有明确的类型,而是从复杂到原始数据类型(甚至从原始数据类型到原始数据类型)will happily type-coerce #39;是否应用了该操作。使用对象(而不是数字)调用上面的函数会在水下执行进一步的函数调用,并且这些函数根本不能保证纯粹(参见Andreas对此的回答)
绝大多数JS函数都不像我们的简单函数。对于大多数功能,我们不仅要证明它们是纯粹的,还要证明它们内部调用的所有功能都是纯粹的。现在我们正在进入halting problem。让我们来看一个荒谬的简单例子:
function(x) {
if (typeof window !== "undefined") {
return x * x;
}
return x * x * x;
}
这是纯粹的吗?好吧,如果我们在浏览器中运行它,那么在浏览器中它是纯粹的,因为始终定义window
。但是在像Node.js这样的东西中,可能是纯粹的,但它可能不是:我们无法证明它是,也不能证明它不是,因为我们无法证明这一点函数运行时存在神秘的window
变量。虽然Node.js没有全局window
变量,但我们可以随时轻松地引入一个变量,并且函数的行为会发生变化。现在我们突然面临证明我们的整个代码是否会引入window
变量(并且可能非常有创意地完成,例如global["win" + _abc] = true
其中_abc
是"dow"
字符串import Cocoa
class CustomTextFieldCell: NSTextFieldCell {
// When the background changes (as a result of selection/deselection) switch appropriate colours
override var backgroundStyle: NSBackgroundStyle {
didSet {
if (backgroundStyle == NSBackgroundStyle.Dark) {
if self.textColor == NSColor.redColor() {
self.textColor = NSColor.yellowColor()
}
} else if (backgroundStyle == NSBackgroundStyle.Light) {
if (self.textColor == NSColor.yellowColor()) {
self.textColor = NSColor.redColor()
}
}
}
}
// When the colour changes, switch to a better alternative for the cell's current background
override var textColor: NSColor? {
didSet {
if let colour = self.textColor {
if backgroundStyle == NSBackgroundStyle.Dark {
if self.textColor == NSColor.redColor() {
self.textColor = NSColor.yellowColor()
}
} else if backgroundStyle == NSBackgroundStyle.Light {
if (self.textColor == NSColor.yellowColor()) {
self.textColor = NSColor.redColor()
}
}
}
}
}
}
)。这是一个失败的原因。
兔子洞深入,阅读停止问题将让你了解停止问题面临多少差异。
答案 1 :(得分:2)
即使你的第一个函数也不纯,因为在JavaScript pictureBox1.ImageLocation = reader["imagem"].ToString();
pictureBox1.Height = pictureBox1.Image.Height;
pictureBox1.Width = pictureBox1.Image.Width;
/*mw and mh are the main width and main heigth,
i used this in case the user selects another
image, then the window returns to it's original
size before changing again.*/
this.Height = mh;
this.Width = mw;
this.Height += pictureBox1.Image.Height;
this.Width = pictureBox1.Image.Width + 16;
if (this.Width < mw)
{
this.Width = mw;
}
this.CenterToParent();
中可以调用ToNumber转换,如果*
恰好是具有用户定义的x
,则可以调用任意用户代码。 {1}}或toString
方法,或者有人碰巧使用valueOf
进行修补。
可悲的事实是,在JS中几乎没有任何可以被证明是纯粹的。唯一不会产生副作用的操作是Object.prototype
,===
,!==
,!
,&&
和||
。对于编译器中的优化来说,这是一个巨大的问题,顺便说一句。
答案 2 :(得分:1)
示例代码。限制:只能*猜测*如果某些代码是纯的=只能给出提示,但不能保证
/* programmatically guess if some javascript code is pure or impure
npm install acorn acorn-walk
license is CC0-1.0 */
const acorn_parse = require("acorn").parse;
const acorn_walk = require("acorn-walk");
// the code to analyze
const content = `
['k1'].map(key => {
const val = data[key];
let local1;
var local2 = 2;
var local3, local4;
global1[key] = val; // find me
global2.key = val; // find me
global3.push(val); // find me
global4.pop(); // find me
global5.shift(); // find me
global6 = 'impure'; // find me
const local7 = global7[1234];
var local8 = global8.map(x => 2*x);
var local9 = global9.filter(Boolean);
const local10 = global10.pop(); // find me
local1 = 'ok';
local2.push('ok');
return [key, val];
})
`;
// method names for our educated guess
const write_method_set = new Set(['push', 'pop', 'shift']);
const read_method_set = new Set(['map', 'filter', 'reduce', 'forEach']);
const is_write_method = method_name => write_method_set.has(method_name);
const is_read_method = method_name => read_method_set.has(method_name);
const is_local = name => (name != undefined && local_var_set.has(name));
const get_src = node => content.substring(node.start, node.end);
function test_assign(node, left_name) {
if (left_name == undefined) {
console.log(`TODO implement: detect write access in:`);
console.dir(node);
return;
}
if (!is_local(left_name)) console.log(`impure write access to global ${left_name}: ${get_src(node)}`);
else console.log(`pure? write access to local ${left_name}: ${get_src(node)}`);
}
function test_call(node, left_name, method_name) {
if (left_name == undefined) {
console.log(`TODO implement: detect write access in:`)
console.dir(node);
return;
}
if (is_read_method(method_name)) return console.log(`pure? access to ${left_name}: ${get_src(node)}`);
if (!is_local(left_name)) {
if (is_write_method(method_name)) console.log(`impure write access to global ${left_name}: ${get_src(node)}`);
else console.log(`pure? access to global ${left_name}: ${get_src(node)}`);
}
else console.log(`pure? write access to local ${left_name}: ${get_src(node)}`)
}
const local_var_set = new Set();
// throws on syntax error
let ast = acorn_parse(content, { ecmaVersion: 2020, sourceType: "module" });
acorn_walk.full(ast, (node, state, type) => {
if (node.type == 'VariableDeclaration') {
node.declarations.forEach(d => {
local_var_set.add(d.id.name);
console.log(`declare local: ${d.id.name}`);
});
}
else if (node.type == 'AssignmentExpression') {
const left_name =
node.left.type == 'Identifier' ? node.left.name :
node.left.type == 'MemberExpression' ? node.left.object.name :
undefined
;
test_assign(node, left_name);
}
else if (node.type == 'CallExpression') {
if (node.callee.object.type == 'ArrayExpression') return; // simply ignore
const left_name =
node.callee.type == 'MemberExpression' ? node.callee.object.name :
undefined
;
const method_name =
node.callee.type == 'MemberExpression' ? node.callee.property.name :
undefined
;
test_call(node, left_name, method_name);
}
//else console.dir(node);
});
样本输出
$ node test.js | grep impure
impure write access to global global1: global1[key] = val
impure write access to global global2: global2.key = val
impure write access to global global3: global3.push(val)
impure write access to global global4: global4.pop()
impure write access to global global5: global5.shift()
impure write access to global global6: global6 = 'impure'
impure write access to global global10: global10.pop()