这个 JavaScript 函数,采用可变引用参数,是一个纯函数吗?
Is this JavaScript function, taking a mutable reference argument, a pure function?
我有与 相同的问题,但在 JavaScript 的上下文中。
来自 Wikipedia:
[a pure function's] return value is the same for the same arguments
那里进一步声称纯函数不允许在 return 值中使用“可变引用参数”进行变化。在 JavaScript 中,每个普通对象都作为“可变引用参数”传递。考虑以下示例:
const f = (arr) => arr.length
const x = []
console.log( f(x) ) // 0
x.push(1);
console.log( f(x) ) // 1
以上证明f
是不纯的吗?
或者你会争辩说我们在这两种情况下没有使用“相同”参数调用 f
吗?
我可以看到在 language/environment 中调用 f
不纯是多么有意义,其中其他线程可能会在 f
执行时混淆可变引用参数。但由于 f
不是 async
,因此不可能发生这种情况。 x
从 f
被调用的那一刻到它完成执行时将保持不变。 (如果我没有理解错的话,Verifiable Functional Purity in Java 的第 4.1 节中提出的“相同”的定义似乎支持这种解释。)
还是我遗漏了什么? JavaScript 中是否有一个示例,其中不包含异步代码的函数丢失了 referential transparency 的 属性 仅仅是因为它采用了可变引用,但是如果我们使用例如Immutable.js 数据结构?
当严格按照 Wikipedia definition 时,将对可变数据结构(例如本机数组)的引用作为参数的函数是不纯的:
Its return value is the same for the same arguments (no variation with local static variables, non-local variables, mutable reference arguments or input streams from I/O devices).
等效
虽然这清楚地说“可变引用参数没有变化”,但我们可以说这是可以解释的,取决于 “相同” 和 [=86] 的含义=]“变异”。可能有不同的定义,因此我们进入意见领域。引自您提到的论文:
There is not a single obviously right answer to these questions. Determinism is thus a parameterized property: given a definition of what it means for arguments to be equivalent, a method is deterministic if all calls with equivalent arguments return results that are indistinguishable from within the language
同一篇论文中提出的功能纯度,使用了以下等价定义:
Two sets of object references are considered equivalent if they result in identical object graphs
根据该定义,以下两个数组被认为是等价的:
let a = [1];
let b = [1];
但是如果不添加更多限制,这个概念不能真正应用于 Java脚本。也不是 Java,这就是本文作者提到一种精简语言 Joe-E 的原因:
objects have identity: conceptually, they have an “address”, and we can compare whether two object references point to the same “address” using the ==
operator. This notion of object identity can expose nondeterminism.
插图在Java脚本中:
const compare = (array1, array2) => array1 === array2;
let arr = [1];
let a = compare(arr, arr);
let b = compare(arr, [1]);
console.log(a === b); // false
由于两次调用 return 的结果不同,即使参数具有相同的形状和内容,我们应该得出结论(根据等价的定义)上述函数 compare
不是纯的。虽然在 Java 中你可以影响 ==
运算符的行为(Joe-E 禁止调用 Object.hashCode
),因此避免这种情况发生,这在 [=94= 中通常是不可能的]比较对象时的脚本。
意想不到的副作用
另一个问题是 JavaScript 不是强类型的,因此函数不能确定它接收的参数是它们预期的。例如,下面的函数看起来很纯粹:
const add = (a, b) => a + b;
但它可以以产生副作用的方式调用:
const add = (a, b) => a + b;
let i = 0;
let obj = { valueOf() { return i++ } };
let a = add(1, obj);
let b = add(1, obj);
console.log(a === b); // false
你问题中的函数存在同样的问题:
const f = (arr) => arr.length;
const x = { get length() { return Math.random() } };
let a = f(x);
let b = f(x);
console.log(a === b) // false
在这两种情况下,函数都无意中调用了一个不纯函数,并 return 生成了一个依赖于它的结果。虽然在第一个示例中,通过 typeof
检查仍然很容易使函数成为纯函数,但这对您的函数来说并不那么微不足道。我们可以想到 instanceof
或 Array.isArray
,甚至是一些智能的 deepCompare
函数,但是,调用者仍然可以设置一个奇怪对象的原型,设置其构造函数 属性,替换原始属性使用吸气剂,将对象包装在代理中,...等等,甚至欺骗最聪明的相等检查器。
实用主义
正如在 Java脚本中一样,有太多的“未解决的问题”,必须务实才能对“纯”有一个有用的定义,否则几乎没有什么可以被标记为纯。
例如,在实践中很多人会调用像 Array#slice
这样的纯函数,即使它存在上述问题(包括与特殊参数 this
相关的问题)。
结论
在 JavaScript 中,当调用纯函数时,您通常必须就函数调用方式达成一致。参数应该是某种类型的,并且没有(隐藏的)可以调用但不纯的方法。
有人可能会争辩说这违背了“纯”背后的想法,“纯”应该只由函数定义本身决定,而不是它最终可能被调用的方式。
我有与
来自 Wikipedia:
[a pure function's] return value is the same for the same arguments
那里进一步声称纯函数不允许在 return 值中使用“可变引用参数”进行变化。在 JavaScript 中,每个普通对象都作为“可变引用参数”传递。考虑以下示例:
const f = (arr) => arr.length
const x = []
console.log( f(x) ) // 0
x.push(1);
console.log( f(x) ) // 1
以上证明f
是不纯的吗?
或者你会争辩说我们在这两种情况下没有使用“相同”参数调用 f
吗?
我可以看到在 language/environment 中调用 f
不纯是多么有意义,其中其他线程可能会在 f
执行时混淆可变引用参数。但由于 f
不是 async
,因此不可能发生这种情况。 x
从 f
被调用的那一刻到它完成执行时将保持不变。 (如果我没有理解错的话,Verifiable Functional Purity in Java 的第 4.1 节中提出的“相同”的定义似乎支持这种解释。)
还是我遗漏了什么? JavaScript 中是否有一个示例,其中不包含异步代码的函数丢失了 referential transparency 的 属性 仅仅是因为它采用了可变引用,但是如果我们使用例如Immutable.js 数据结构?
当严格按照 Wikipedia definition 时,将对可变数据结构(例如本机数组)的引用作为参数的函数是不纯的:
Its return value is the same for the same arguments (no variation with local static variables, non-local variables, mutable reference arguments or input streams from I/O devices).
等效
虽然这清楚地说“可变引用参数没有变化”,但我们可以说这是可以解释的,取决于 “相同” 和 [=86] 的含义=]“变异”。可能有不同的定义,因此我们进入意见领域。引自您提到的论文:
There is not a single obviously right answer to these questions. Determinism is thus a parameterized property: given a definition of what it means for arguments to be equivalent, a method is deterministic if all calls with equivalent arguments return results that are indistinguishable from within the language
同一篇论文中提出的功能纯度,使用了以下等价定义:
Two sets of object references are considered equivalent if they result in identical object graphs
根据该定义,以下两个数组被认为是等价的:
let a = [1];
let b = [1];
但是如果不添加更多限制,这个概念不能真正应用于 Java脚本。也不是 Java,这就是本文作者提到一种精简语言 Joe-E 的原因:
objects have identity: conceptually, they have an “address”, and we can compare whether two object references point to the same “address” using the
==
operator. This notion of object identity can expose nondeterminism.
插图在Java脚本中:
const compare = (array1, array2) => array1 === array2;
let arr = [1];
let a = compare(arr, arr);
let b = compare(arr, [1]);
console.log(a === b); // false
由于两次调用 return 的结果不同,即使参数具有相同的形状和内容,我们应该得出结论(根据等价的定义)上述函数 compare
不是纯的。虽然在 Java 中你可以影响 ==
运算符的行为(Joe-E 禁止调用 Object.hashCode
),因此避免这种情况发生,这在 [=94= 中通常是不可能的]比较对象时的脚本。
意想不到的副作用
另一个问题是 JavaScript 不是强类型的,因此函数不能确定它接收的参数是它们预期的。例如,下面的函数看起来很纯粹:
const add = (a, b) => a + b;
但它可以以产生副作用的方式调用:
const add = (a, b) => a + b;
let i = 0;
let obj = { valueOf() { return i++ } };
let a = add(1, obj);
let b = add(1, obj);
console.log(a === b); // false
你问题中的函数存在同样的问题:
const f = (arr) => arr.length;
const x = { get length() { return Math.random() } };
let a = f(x);
let b = f(x);
console.log(a === b) // false
在这两种情况下,函数都无意中调用了一个不纯函数,并 return 生成了一个依赖于它的结果。虽然在第一个示例中,通过 typeof
检查仍然很容易使函数成为纯函数,但这对您的函数来说并不那么微不足道。我们可以想到 instanceof
或 Array.isArray
,甚至是一些智能的 deepCompare
函数,但是,调用者仍然可以设置一个奇怪对象的原型,设置其构造函数 属性,替换原始属性使用吸气剂,将对象包装在代理中,...等等,甚至欺骗最聪明的相等检查器。
实用主义
正如在 Java脚本中一样,有太多的“未解决的问题”,必须务实才能对“纯”有一个有用的定义,否则几乎没有什么可以被标记为纯。
例如,在实践中很多人会调用像 Array#slice
这样的纯函数,即使它存在上述问题(包括与特殊参数 this
相关的问题)。
结论
在 JavaScript 中,当调用纯函数时,您通常必须就函数调用方式达成一致。参数应该是某种类型的,并且没有(隐藏的)可以调用但不纯的方法。
有人可能会争辩说这违背了“纯”背后的想法,“纯”应该只由函数定义本身决定,而不是它最终可能被调用的方式。