什么是参考透明度?

引用透明性是什么意思?我曾听人描述它为“这意味着你可以用等号替换等号”,但这似乎是一个不充分的解释。

63329 次浏览

引用透明函数的作用类似于数学函数;给定相同的输入,它总是会产生相同的输出。它意味着传入的状态没有被修改,并且函数本身没有状态。

一个表达式是引用透明的,如果它可以用它的值替换,而不改变算法,产生的算法在相同的输入上具有相同的效果和输出。

引用透明性是函数式编程中常用的术语,它意味着给定一个函数和一个输入值,您将始终收到相同的输出。也就是说,函数中没有使用外部状态。

下面是一个引用透明函数的例子:

int plusOne(int x)
{
return x+1;
}

对于引用透明函数,给定一个输入和一个函数,您可以用一个值替换它,而不是调用函数。所以我们不用参数5来调用+ one,我们可以用6来代替它。

另一个很好的例子是一般的数学。在数学中,给定一个函数和一个输入值,它总是映射到相同的输出值。F (x) = x + 1。因此,数学中的函数是指透明的。

这个概念对研究人员来说很重要,因为它意味着当您拥有一个引用透明的函数时,它有助于实现简单的自动并行化和缓存。

引用透明性总是用在像Haskell这样的函数式语言中。

--

与之相反的是参照不透明的概念。这句话的意思正好相反。调用该函数可能并不总是产生相同的输出。

//global G
int G = 10;


int plusG(int x)
{//G can be modified externally returning different values.
return x + G;
}

另一个例子是面向对象编程语言中的成员函数。成员函数通常对其成员变量进行操作,因此是引用不透明的。成员函数当然可以是引用透明的。

还有一个例子是从文本文件中读取并打印输出的函数。这个外部文本文件可以随时更改,因此该函数将是引用不透明的。

引用透明函数是只依赖于其输入的函数。

如果你对词源感兴趣(比如。为什么这个概念有这个特殊的名字),看看我的博客关于这个主题。这个术语来自哲学家/逻辑学家奎因。

术语“引用透明性”来自分析哲学,这是哲学的一个分支,它基于逻辑和数学的方法分析自然语言结构、语句和参数。换句话说,它是计算机科学之外最接近编程语言语义的学科。哲学家威拉德奎因提出了参照透明的概念,但它也隐含在伯特兰·罗素和阿尔弗雷德·怀特黑德的方法中。

就其核心而言,“参考透明度”是一个非常简单明了的概念。术语“referent”在分析哲学中用于谈论表达式所指向的事物。它与我们在编程语言语义中所说的“意义”或“外延”大致相同。使用Andrew Birkett的例子(博客),术语“苏格兰的首都”指的是爱丁堡市。这是“referent”的一个简单例子。

一个句子中的上下文是“引用透明的”,如果用另一个指同一实体不改变意思的术语替换该上下文中的一个术语。例如

苏格兰议会在苏格兰首都开会。

意思和

苏格兰议会在爱丁堡开会。

因此,“苏格兰议会在……开会”是一个指涉透明的上下文。我们可以把“苏格兰的首府”换成“爱丁堡”而不改变它的意思。换句话说,上下文只关心术语所指的内容,而不关心其他内容。也就是说,上下文是“引用透明的”。

另一方面,在句子中,

自1999年以来,爱丁堡一直是苏格兰的首府。

我们不能做这样的替换。如果我们这样做,我们会得到“Edinburgh has been Edinburgh since 1999”,这是一个疯狂的说法,并且不能传达与原句子相同的意思。所以,“Edinburgh has been…”“自1999年以来”是指不透明的(指透明的反义词)。它显然关心更多的东西而不是术语所指的内容。是什么?

像“苏格兰的首都”这样的词被称为明确的条款,在很长一段时间里,它们并没有给逻辑学家和哲学家带来多少头痛。Russell和Quine把它们整理出来,说它们实际上不是“指涉的”,也就是说,认为上面的例子是用来指实体的是错误的。理解“爱丁堡自1999年以来一直是苏格兰的首都”的正确方法是说

苏格兰自1999年以来就有了首都,那就是爱丁堡。

这个句子不能变成一个疯狂的句子。问题解决了!奎因的观点是,自然语言是混乱的,或至少是复杂的,因为它是为了方便实际使用而设计的,但哲学家和逻辑学家应该通过正确的方式理解它们,从而使它们变得清晰。引用透明性是用于将这样的清晰的含义

这一切和编程有什么关系呢?其实不是很多。如前所述,引用透明性是用于理解语言的工具,即用于赋值意义克里斯托弗•斯特雷奇,谁创立了编程语言语义领域,使用它在他的意义研究。他的基础论文“编程语言的基本概念”可以在网上找到。这是一篇漂亮的论文,每个人都能阅读和理解它。所以,请这样做。你会开窍的。他在这一段中介绍了“参考透明度”一词:

表达式最有用的属性之一是奎因所指的属性 透明度。本质上,这意味着如果我们希望找到一个表达式的值 包含子表达式,关于子表达式我们唯一需要知道的是它的 价值。子表达式的任何其他特征,例如它的内部结构、数字 它的组成部分的性质,它们被评估的顺序,或者墨水的颜色

.

.

.

“本质上”这个词的使用表明,斯特雷奇是为了用简单的术语来解释它。函数式程序员似乎用他们自己的方式理解这段话。论文中还出现了9个“参考透明度”,但它们似乎对其他任何一个都没有影响。事实上,Strachey的整篇论文都致力于解释命令式编程语言的含义。但是,现在函数式程序员声称命令式编程语言是引用透明的。斯特雷奇会在坟墓里翻来覆去。

我们可以挽救局面。我们说过自然语言是“混乱的,或者至少是复杂的”,因为它是为了方便实际使用而设计的。编程语言也是如此。它们是“混乱的,或者至少是复杂的”,因为它们是为了方便实际使用而设计的。这并不意味着他们需要迷惑我们。它们只需要以正确的方式被理解,使用一种指涉透明的元语言,这样我们就能清楚地理解它们的意思。在我引用的论文中,斯特雷奇正是这么做的。他通过将命令式编程语言分解为基本概念来解释它们的意义,在任何地方都不含糊。他分析的一个重要部分是指出编程语言中的表达式有两种“值”,称为l-values热阻。在斯特雷奇的论文发表之前,人们并不理解这一点,困惑盛行。今天,C的定义经常提到它,每个C程序员都了解其中的区别。(其他语言的程序员是否同样理解它,很难说。)

奎因和斯特雷奇都关注涉及某种形式的上下文依赖的语言结构的意义。例如,我们的例子“爱丁堡自1999年以来一直是苏格兰的首都”表明了这样一个事实:“苏格兰的首都”取决于它被考虑的时间。在自然语言和编程语言中,这种上下文依赖是现实存在的。即使在函数式编程中,自由变量和绑定变量也要根据它们所处的上下文进行解释。任何类型的上下文依赖都以某种方式阻碍了引用透明性。如果你试图理解术语的意思而不考虑它们所依赖的上下文,你会再次陷入困惑。奎因关注的是模态逻辑的意义。他认为模态逻辑是指涉不透明的,应该通过将其转换为指涉透明的框架来清理(例如,将必要性视为可证明性)。他基本上输掉了这场辩论。逻辑学家和哲学家都认为Kripke的可能世界语义是完全充分的。类似的情况也适用于命令式编程。Strachey解释的状态依赖和Reynolds解释的存储依赖(在某种程度上类似于Kripke的可能世界语义)是完全足够的。函数式程序员对这方面的研究了解不多。他们关于参考透明度的想法不能完全相信。

[附加说明:上面的例子说明了一个简单的短语,如“苏格兰的首都”有多个层次的意义。在一个层面上,我们现在谈论的可能是首都。在另一个层面上,我们可以讨论苏格兰在这段时间里可能拥有的所有首都。在通常的实践中,我们可以“放大”到一个特定的上下文,也可以“缩小”到跨越所有上下文。自然语言的效率利用了我们这样做的能力。命令式编程语言在很大程度上也是如此。我们可以在赋值函数的右边使用变量x (热阻)来讨论它在特定状态下的值。或者,我们可以讨论它的l值,它横跨所有状态。人们很少会被这样的事情弄糊涂。然而,它们不一定能够准确地解释语言结构中固有的所有层次的意义。所有这些层次的意义并不一定是“明显的”,正确地研究它们是一个科学问题。然而,普通人无法解释这种层次分明的含义并不意味着他们对此感到困惑。]

下面的一个单独的“后记”将这个讨论与函数式编程和命令式编程的关注点联系起来

请注意,这个“意义”的概念是发生在观察者头脑中的事情。因此,同样的“参考”对不同的人可能意味着不同的事情。例如,我们在维基百科上有一个爱丁堡消歧页面。

在编程上下文中出现的一个相关问题可能是多态性。

也许我们应该为特殊情况下的多态(或者甚至是强制转换)取一个名字,其中不同的多态情况在语义上是等价的(而不是完全相似)。例如,数字1——可以用整数类型、复杂类型或任何其他类型表示——可以用多态方式处理)。

对于那些需要简明解释的人,我将冒险给出一个解释(但请阅读下面的披露)。

编程语言中的引用透明性促进了等式推理——您拥有的引用透明性越多,就越容易进行等式推理。例如,使用(伪)函数定义,

F x = x + x,

在这个定义的范围内,您可以(安全地)将f(foo)替换为foo + foo,而不会对在哪里执行此简化有太多限制,这很好地说明了您的编程语言具有多大的引用透明性。

例如,在C编程的意义上,如果foo是x++,那么你就不能安全地执行这个约简(也就是说,如果你要执行这个约简,你最终得到的程序将与你开始时的程序不同)。

在实际的编程语言中,你不会看到完美的引用透明性,但函数式程序员比大多数人更关心它(参考Haskell,它是一个核心目标)。

(完全披露:我是一个函数式程序员,所以从上面的答案你应该对这个解释持保留态度。)

[这是我3月25日的回答的后记,旨在使讨论更接近函数式/命令式编程的关注点。]

函数式程序员关于引用透明性的想法似乎在三个方面不同于标准的概念:

  • 哲学家/逻辑学家使用“引用”、“外延”、“指示”和“bedeutung”(Frege的德语术语)这样的术语,而函数式程序员使用术语“值”。(这并不完全是他们做的。我注意到,兰丁、斯特雷奇和他们的后人也用“价值”一词来谈论引用/外延。这可能只是兰丁和斯特雷奇引入的术语简化,但如果以一种天真的方式使用,它似乎会产生很大的不同。)

  • 函数式程序员似乎相信这些“值”存在于编程语言内部,而不是外部。在这一点上,他们不同于哲学家和编程语言语义学家。

  • 他们似乎相信这些“价值”应该是通过评价获得的。

例如,维基百科关于引用透明性的文章说,今天早上:

如果一个表达式可以用它的值替换而不改变程序的行为(换句话说,在相同的输入上产生相同的效果和输出的程序),那么这个表达式就是引用透明的。

这与哲学家/逻辑学家所说的完全不同。他们说,如果上下文中的表达式可以被另一个引用同一事物的表达式 (互参的表达式)替换,则该上下文是引用透明的或引用透明的。这些哲学家/逻辑学家是谁?它们包括弗雷格罗素怀特海德Carnap奎因教堂等等。他们每个人都是高大上的人物。这些逻辑学家的智力结合起来至少可以说是惊天动地的。所有这些都是一致的立场,指涉/外延存在于形式语言之外,语言内的表达式只能谈论关于它们。因此,在语言中所能做的就是将一个表达式替换为引用同一实体的另一个表达式。引用/外延本身存在于语言中。为什么函数式程序员会偏离这个公认的传统呢?

有人可能会认为编程语言语义学家可能误导了他们。但是,他们没有。

Landin:

(a)每个表达式有一个 嵌套子表达式结构,(b)每个子表达式 表示某些东西(通常是数字、真值或 数值函数), (c)表达式表示的东西, 也就是说,它的“值”只取决于它的子元素的值 表达式,而不是它们的其他属性。(添加强调)< / p >

沙砾堕落:

关于表达式,唯一重要的是它的值,任何子表达式都可以 替换为任何其他价值相等的[增加了重点]。而且,在一定范围内,表达式的值无论何时出现都是相同的".

伯德和瓦德勒:

表达式的值仅取决于其组成部分的值 表达式(如果有)和这些子表达式可以被others自由替换 拥有相同的值< / em >[添加强调]. < / p >

因此,回想起来,兰丁和斯特雷奇试图用“价值”取代“参考”/“外延”来简化术语的努力可能是不明智的。一听到“价值”这个词,人们就很容易想到一个产生“价值”的评估过程。将计算产生的任何东西看作“值”也是很诱人的,尽管很明显那不是外延。这就是我收集到的发生在函数式程序员眼中的“引用透明性”概念上的情况。但是早期语义学家所说的“值”是——一个求值的结果或一个函数的输出或任何类似的东西。它是术语的外延。

一旦我们将一个表达式(经典哲学家话语中的“引用”或“外延”)的所谓“价值”理解为一个复杂的数学/概念对象,各种可能性就会打开。

  • 正如我在3月25日的回答中提到的,Strachey将命令式编程语言中的变量解释为L-values,这是一个复杂的概念对象,在编程语言的语法中没有直接的表示。
  • 他还将这些语言中的命令解释为状态到状态函数,这是复杂数学对象的另一个实例,在语法中不是“值”。
  • 即使是C中的副作用函数调用也有一个定义良好的“值”,作为将状态映射到状态和值对的状态转换器(在函数式程序员的术语中称为“单子”)。

函数式程序员不愿意称这些语言为“参照透明”,这仅仅意味着他们不愿意承认这些复杂的数学/概念对象为“值”。另一方面,他们似乎完全愿意把一个状态转换器称为一个“值”,当它被放在他们自己最喜欢的语法中,并用“单子”这样的流行词装扮起来。我不得不说,他们是完全不一致的,即使我们承认他们的“参考透明度”的想法有一定的一致性。

历史可以让我们了解这些困惑是如何产生的。1962年到1967年对克里斯托弗·斯特雷奇来说是非常紧张的一段时间。1962年至1965年间,他在莫里斯·威尔克斯(Maurice Wilkes)手下做兼职研究助理,设计和实现后来被称为cpls的编程语言。这是一种命令式编程语言,但同时也具有强大的函数式编程语言功能。兰丁是斯特雷奇咨询公司的雇员,他对斯特雷奇对编程语言的看法产生了巨大的影响。在1965年里程碑式的论文“未来700种编程语言”中,Landin毫不讳言地提倡函数式编程语言(称它们为指示的语言),并将命令式编程语言描述为它们的“对立物”。在随后的讨论中,我们发现斯特雷奇对兰丁的强势地位提出了质疑。

< p >…DLs形式 所有语言的子集。他们是一个有趣的子集,但只有一个 除非你习惯了,否则使用起来很不方便。我们需要 因为目前我们不知道如何构造 使用包含命令和跳转的语言进行证明。(添加强调)< / p >
1965年,斯特雷奇在牛津大学担任读者,似乎几乎把全部时间都用于发展命令式和跳跃式理论。1967年,他提出了一个理论,并在哥本哈根暑期学校的“编程语言的基本概念”课程中教授了这个理论。课堂笔记本应已经出版,但“不幸的是,由于拖延 编辑过程中,程序从未实现;就像 然而,斯特雷奇在牛津大学的大部分作品 报纸有很有影响力的私人发行量。”(马丁Campbell-Kelly) < / p >

由于人们依赖二手资料和道听途说,很难获得斯特雷奇的作品可能会导致混淆的传播。但是,既然“基本概念”在网上很容易找到,就没有必要依靠猜测工作了。我们应该读一读,然后对斯特雷奇的意思作出自己的判断。特别是:

  • 在第3.2节中,他讨论了“表达式”,并谈到了“r值参考透明度”。
  • 他的3.3节讨论了“命令”,其中谈到了“l值参考透明性”。
  • 在第3.4.5节中,他谈到了“函数和例程”,并声明“在r值上下文中,任何r值参考透明度的偏离都应该 可以通过将表达式分解为几个更简单的命令来消除 表达式,或者(如果这变得困难的话)注释的主题。”李< / >

如果不理解l值、r值和其他填充命令式程序员概念宇宙的复杂对象之间的区别,任何关于“引用透明性”的讨论从根本上都是错误的。

  1. 表示性语义是基于建模语言,通过构建构成可表示性的域。
  2. 函数式程序员使用术语价值来描述基于语言重写规则的计算收敛。它的操作语义。

在1中,有两种语言的清晰度:

  • 被建模的那个,目标语言
  • 建模的语言,元语言

2、由于对象和元语言的紧密性,它们可能会被混淆。

作为一名语言实现者,我发现我需要经常记住这个区别。

所以Reddy教授,我可以这样解释你吗?

在函数式编程和语义的上下文中,术语是指涉的 透明度是不透明的
下面的答案我希望能补充并限定有争议的第一个和第三个 答案。< / p > 让我们承认一个表达式表示或指向 一些指示物。然而,问题是这些引用是否可以同构编码为表达式本身的一部分,称这样的表达式为“值”。例如,字面数值是算术表达式集的子集,真值是布尔表达式集的子集,等等。其思想是计算表达式的值(如果它有值)。因此,“值”一词可以指一种外延,也可以指表达式集中的一个特殊元素。但如果有一个同构(双射)之间的指称和值,我们 可以说它们是一样的。(也就是说,人们必须小心定义 指涉物与同构,由所指场证明 语义。举一个在第三个答案的回答中提到的例子 代数数据类型定义data Nat = Zero | Suc Nat没有 与预期的自然数集对应)

让我们用E[·]来表示带洞的表达式,在某些地方也知道 作为一个“上下文”。类c表达式的两个上下文示例是[·]+1[·]++ . < / p > 让我们为接受表达式(没有空)的函数编写[[·]] 并在某些方面传达其意义(指涉物、外延等) meaning-providing宇宙。(我借用了这个领域的符号

让我们将Quine的定义稍微正式一些,如下所示 如果给定任意两个表达式E1E2(没有孔 这样[[E1]] = [[E2]](即表达式表示/指向 相同的referent),则是[[E[E1]]] = [[E[E2]]](即填充 包含E1E2的空孔的结果也表示相同的表达式 referent)。< / p > 莱布尼茨用等号替换等号的规则通常用“if”表示 E1 = E2然后E[E1] = E[E2]',这表明E[·]是一个函数。一个函数 (或者计算函数的程序)是一个映射 源到目标,以便每个源最多有一个目标元素 元素。非确定性函数是名不符实的,它们要么是关系, 函数传递集合,等等。如果在莱布尼茨规则中,等式=为 那么双括号就被认为是理所当然的 忽略掉。引用透明上下文是一个函数。而莱布尼茨规则是方程推理的主要组成部分,所以方程推理肯定与参考透明度有关 虽然[[·]]是一个从表达式到外延的函数,但它可以是一个 函数从表达式到“值”被理解为一个受限制的子集

.表达式,[[·]]可以理解为求值

现在,如果E1是一个表达式,E2是一个值,我们就拥有了我认为大多数人在表达式、值和求值方面定义引用透明性时的意思。但正如本页第1和第3个答案所说明的那样,这是一个不准确的定义。

[·]++这样的上下文的问题不是副作用,而是它的值在C语言中定义的意义不是同构的。函数是 不是值(好吧,指向函数的指针是),而在函数式编程语言中它们是。Landin, 斯特雷奇和表示性语义学的先驱们都很聪明

.使用功能世界来提供意义 对于命令式c类语言,我们可以(粗略地)提供语义

.使用函数[[·]] : Expression -> (State -> State x Value) ValueExpression的子集。State包含对 (标识符值)。语义函数接受一个表达式并以 它的意思是从当前状态到已更新的pair的函数 状态和值。例如,[[x]]是来自当前状态的函数 第一个组件是当前状态,第二个组件是当前状态的对 [[x++]]是x的值 对象对的当前状态,该对象对的第一个组件是 x的第二个分量就是这个值。在这个 意义上,上下文[·]++是引用透明的,如果它满足 上面给出的定义。< / p > 我认为函数式程序员有权在 它们自然地将[[·]]作为函数从表达式恢复到值。 函数是一类值,状态也可以是值,而不是 外延。状态单子(在某种程度上)是一种用于传递(或

.线程)状态

引用透明性可以简单地表述为:

  • 在任何上下文中总是求值为相同结果的表达式[1]
  • 一个函数,如果给定相同的参数两次,必须产生相同的结果两次[2]

例如,编程语言Haskell是一种纯函数式语言;这意味着它是引用透明的。

参考透明度是计算机科学中使用的一个术语。它起源于数学逻辑,但在计算机科学中具有广泛的应用和有效的含义。

它的意思是:一个构造(如函数)它可以在不改变其含义的情况下被其结果所取代。

在通常使用中,它类似于单纯的表情,但并不完全等效。纯表达式完全由其他纯表达式组成。引用透明表达式可能在内部是不纯的,例如在其计算过程中使用可变状态,但在表达式整体之外没有副作用。

所有纯函数,根据它们的构造,都是指涉透明的,但不一定相反。

许多语言特性支持不纯引用透明性,例如Haskell中的ST单表,c++中的__abc1和某些lambdas。

有时引用透明性是强制的,而其他时候程序员必须自己保证它。

当我读到被接受的答案时,我以为我在不同的页面上,而不是在stackoverflow上。

引用透明性是定义纯函数的一种更正式的方式。因此,如果一个函数在相同的输入上始终产生相同的结果,则称其为并包含透明

let counter=0
function count(){
return counter++
}

这是不透明的,因为返回值取决于外部变量“counter”。它一直在变化。

这是我们如何使它的参考透明:

function count(counter){
return counter+1
}

现在这个函数是稳定的,并且在提供相同的输入时总是返回相同的输出。

编程中的引用透明性指的是,当一个函数提供了一个输入时,它总是会为给定的输入返回相同的值。以下面的示例函数为例。

Int plusFive(int x){


return x+5


}

对于给定的输入整数x,该函数总是返回相同的值。上述函数的输出可以被其返回值所替代,并且代码的操作应该相同。例如,如果x=10,那么代码可以写成:

Int output = plusFive(10)

Int output = 15