1

这可能接近哲学,但我认为这是问的正确地方。

假设我有一个创建 ID 列表的函数。这些标识符仅在应用程序内部使用,因此Symbol()这里使用 ES2015 是可以接受的。

我的问题是,从技术上讲,当您要求一个符号时,我想 JS 运行时会创建一个唯一标识符(随机数?内存地址?不确定),为了防止冲突,需要访问全局状态。我不确定的原因是因为“技术上”这个词。我不确定(再次从哲学的角度来看)这是否足以打破 API 呈现的数学抽象。

tl;博士:这是一个例子——

function sentinelToSymbol(x) {
  if (x === -1) return Symbol();
  return x;
}

这个函数是纯的吗?

4

2 回答 2

3

不是真的,不,但它可能并不重要。

表面上,(foo) => Symbol(foo)显得纯洁。虽然运行时可能会执行一些带有副作用的操作,但您永远不会看到它们,即使您Symbol()同时使用相同的参数调用也是如此。但是,Symbol使用相同的参数调用将永远不会返回相同的值,这是主要标准之一(下面的#2)。

MDN 页面

请注意, Symbol("foo") 不会将字符串 "foo" 强制转换为符号。它每次都会创建一个新符号:

Symbol("foo") === Symbol("foo"); // false

只看副作用,(foo) => Symbol(foo)是纯粹的(在运行时之上)。

但是,纯函数必须满足更多标准。来自维基百科

纯函数式函数(或表达式)没有副作用(内存或 I/O)。这意味着纯函数有几个有用的属性,其中许多可以用来优化代码:

  • 如果没有使用纯表达式的结果,可以将其删除,而不影响其他表达式。
  • 如果一个纯函数使用没有副作用的参数调用,则结果相对于该参数列表是恒定的(有时称为引用透明性),即如果再次使用相同的参数调用纯函数,则相同的结果将是返回(这可以启用缓存优化,例如记忆化)。
  • 如果两个纯表达式之间没有数据依赖关系,那么它们的顺序可以颠倒,或者它们可以并行执行并且它们不会相互干扰(换句话说,任何纯表达式的求值都是线程安全的)。
  • 如果整个语言不允许副作用,则可以使用任何评估策略;这使编译器可以自由地重新排序或组合程序中表达式的评估(例如,使用森林砍伐)。

您可能会争辩说,该列表的前言排除JavaScript 中的所有内容,因为任何操作都可能导致分配内存、更新内部结构等。在最严格的解释中,JS 绝不是纯粹的。这不是很有趣或有用,所以...

此功能符合标准#1。无视结果,(foo) => Symbol(foo)(foo) => ()任何外部观察者相同。

标准 #2 给我们带来了更多麻烦。给定bar = (foo) => Symbol(foo), bar('xyz') !== bar('xyz'), 所以Symbol根本不满足这个要求。保证每次调用时都会返回一个唯一的实例Symbol

继续前进,标准#3 没有问题。您可以Symbol从不同的线程调用而不会发生冲突(并行),并且调用它们的顺序无关紧要。

最后,标准#4 比直接要求更重要,并且很容易满足(JS 运行时会在运行时随机播放所有内容)。

所以:

  • 严格来说,JS 中没有什么是纯的。
  • Symbol()绝对不是纯粹的,因此这个例子也不是。
  • 如果您只关心副作用而不是记忆,那么该示例确实符合这些标准。
于 2016-08-01T18:46:13.223 回答
0

是的,这个函数是不纯的:sentinelToSymbol(-1) !== sentinelToSymbol(-1). 对于纯函数,我们期望这里是相等的。

但是,如果我们在具有对象身份的语言中使用引用透明性的概念,我们可能希望稍微放宽我们的定义。如果考虑function x() { return []; },它是纯的吗?显然x() !== x(),但无论输入如何,该函数始终返回一个空数组,就像一个常量函数。所以我们必须在这里定义的是我们语言中的值的相等性。===操作员可能不是这里的最佳选择(请考虑)NaN。如果数组包含相同的元素,数组是否彼此相等?可能是的,除非它们在某个地方发生了突变。

所以你现在必须为你的符号回答同样的问题。符号是不可变的,这使得这部分很容易。现在我们可以通过它们的 [[Description]] 值(或.toString())来认为它们相等,因此sentinelToSymbol根据该定义将是纯的。

但是大多数语言确实具有允许破坏引用透明度的功能 - 例如,请参阅如何在 Haskell 中打印列表的内存地址。在 JavaScript 中,这将用于===其他相等的对象。它将使用符号作为属性,因为这会检查它们的身份。因此,如果您在程序中不使用此类操作(或至少在外部无法观察到),您可以为您的函数声明纯洁性并将其用于推理您的程序。

于 2016-08-01T18:49:45.303 回答