纯函数可以返回一个符号吗?

时间:2016-08-01 18:26:40

标签: javascript referential-transparency

这可能与哲学有关,但我认为这是正确的问题。

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

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

tl; dr:这是一个例子 -

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

这个功能是纯粹的吗?

2 个答案:

答案 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运行时随着时间的推移将所有内容混合)。

因此:

  • 严格来说,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中,这将在其他方面相同的对象上使用===。它将使用符号作为属性,因为它会检查它们的身份。因此,如果您不在程序中使用此类操作(或至少在外部没有观察到这些操作),您可以为您的功能声明纯度并使用它来重新执行您的程序。