这可能与哲学有关,但我认为这是正确的问题。
假设我有一个创建ID列表的函数。这些标识符仅在内部用于应用程序,因此可以在此处使用ES2015 Symbol()
。
我的问题是,技术上,当你要求一个符号时,我想象JS运行时创建一个唯一的标识符(随机数?内存地址?不确定),以防止碰撞,需要访问全球状态。我不确定的原因是因为这个词,"技术上"。我不确定(再次,从哲学的角度来看)这是否足以打破API所呈现的数学抽象。
tl; dr:这是一个例子 -
function sentinelToSymbol(x) {
if (x === -1) return Symbol();
return x;
}
这个功能是纯粹的吗?
答案 0 :(得分:3)
不是,不,但实际上可能并不重要。
表面上,(foo) => Symbol(foo)
看似纯净。虽然运行时可以执行一些带副作用的操作,但即使您使用相同的参数同时调用Symbol()
,也永远不会看到它们。但是,使用相同参数调用Symbol
将永远不会返回相同的值,这是主要标准之一(下面的#2)。
来自the MDN page:
请注意,符号(" foo")不会强制字符串" foo"成一个符号。它每次都会创建一个新符号:
Symbol("foo") === Symbol("foo"); // false
仅考虑副作用,(foo) => Symbol(foo)
是纯粹的(在运行时之上)。
但是,纯函数必须符合更多标准。 From Wikipedia:
纯功能函数(或表达式)没有副作用(内存或I / O)。这意味着纯函数有几个有用的属性,其中许多可用于优化代码:
- 如果未使用纯表达式的结果,则可以在不影响其他表达式的情况下删除它。
- 如果使用不会产生副作用的参数调用纯函数,则结果对于该参数列表(有时称为参照透明度)是常量,即如果再次使用相同的参数调用pure函数,则相同将返回结果(这可以启用缓存优化,例如memoization)。
- 如果两个纯表达式之间没有数据依赖关系,那么它们的顺序可以颠倒,或者它们可以并行执行,并且它们不会相互干扰(换句话说,任何纯表达式的评估都是线程安全的)。
- 如果整个语言不允许副作用,那么可以使用任何评估策略;这使编译器可以自由地重新排序或组合程序中表达式的评估(例如,使用砍伐森林)。
你可以说该列表的前言排除了JavaScript中的所有,因为任何操作都可能导致内存被分配,内部结构更新等等。在最严格的解释中,JS绝不是纯粹的。这不是很有趣或有用,所以......
此功能符合标准#1。忽略结果,(foo) => Symbol(foo)
和(foo) => ()
与任何外部观察者相同。
标准#2给我们带来了更多麻烦。鉴于bar = (foo) => Symbol(foo)
,bar('xyz') !== bar('xyz')
,Symbol
根本不符合该要求。每次拨打Symbol
时,您都可以获得一个唯一的实例。
继续,标准#3不会导致任何问题。您可以从不同的线程中调用Symbol
,而不会发生冲突(并行),并且调用它们的顺序并不重要。
最后,标准#4更像是一个音符,而不是直接要求,并且很容易满足(JS运行时随着时间的推移将所有内容混合)。
因此:
Symbol()
绝对不是纯粹的,因此这个例子也不是。 答案 1 :(得分:0)
是的,此功能不纯:sentinelToSymbol(-1) !== sentinelToSymbol(-1)
。我们希望这里有一个纯函数的等式。
但是,如果我们在具有对象标识的语言中使用引用透明度的概念,我们可能想稍微放松我们的定义。如果你考虑function x() { return []; }
,它是纯粹的吗?显然x() !== x()
,但是无论输入如何,函数总是返回一个空数组,就像一个常量函数。所以我们在这里定义的是我们语言中的值的相等性。 ===
运算符可能不适合此处(仅考虑NaN
)。如果包含相同的元素,数组是否相等?可能是的,除非他们在某处发生变异。
所以你现在必须为你的符号回答同样的问题。符号是不可变的,这使得这部分变得容易。现在我们可以通过[[Description]]值(或.toString()
)来认为它们相等,因此sentinelToSymbol
将是纯粹的定义。
但是大多数语言都有允许破坏参照透明度的功能 - 例如,请参阅How to print memory address of a list in Haskell。在JavaScript中,这将在其他方面相同的对象上使用===
。它将使用符号作为属性,因为它会检查它们的身份。因此,如果您不在程序中使用此类操作(或至少在外部没有观察到这些操作),您可以为您的功能声明纯度并使用它来重新执行您的程序。