引用透明性对程序员有什么好处?


18

在编程中,引用透明的好处是什么?

RT是功能性和命令性范式之间的主要区别之一,并且功能性范式的倡导者经常将RT用作与命令性范式相比的明显优势。但是,这些拥护者们在他们的所有努力中,从来没有解释过为什么这对我作为程序员来说是有益

当然,他们会对它们的“纯”和“优雅”有自己的学术解释,但是它如何使它比不太“纯”的代码更好呢?它对我的日常编程有什么好处?

注意: 这不是“ 什么是参照透明性”的副本 后者地址的话题是什么是RT的问题,而这个问题解决了它的好处(可能不是那么直观)。




3
引用透明允许您使用方程式推理进行以下操作:1)证明代码的属性,以及2)编写程序。有几本有关Haskell的书,在其中,作者应该如何从希望函数完成的某些方程式开始,然后仅使用方程式推理就可以最终实现该函数的实现,因此肯定是正确的。现在,这在“日常”编程中可以应用多少取决于上下文...
Bakuriu

2
@err您是否喜欢更易于重构的代码,因为您知道两次调用一个函数是否等同于将其值存储在变量中,然后两次使用所述变量?您会说这对您的日常编程有好处吗?
Andres F.

好处是您无需浪费时间考虑参考非透明性。Kinda喜欢变量带来的好处,您无需浪费时间考虑寄存器分配。
user253751 '16

Answers:


37

好处是纯函数使您的代码更易于推理。换句话说,副作用会增加代码的复杂性。

computeProductPrice方法为例。

一个纯方法会要求您提供产品数量,货币等。您知道,只要用相同的参数调用该方法,它将始终产生相同的结果。

  • 您甚至可以缓存它并使用缓存的版本。
  • 您可以使它变得懒惰,并在实际需要时将其延迟发送给您,因为您知道该值不会同时更改。
  • 您可以多次调用该方法,因为它不会产生副作用。
  • 您可以知道方法本身是与参数隔离的,因此可以与世界隔离。

非纯方法的使用和调试将更加复杂。由于它取决于参数以外的变量的状态并可能更改它们,因此,这意味着它可能在多次调用时产生不同的结果,或者在完全不调用或调用得太早或太晚时都不会具有相同的行为。

想象一下,框架中有一个解析数字的方法:

decimal math.parse(string t)

它没有参照透明性,因为它取决于:

  • 用于指定编号系统的环境变量,即Base 10或其他名称。

  • math库中的变量,用于指定要解析的数字的精度。因此,使用的值1,解析字符串"12.3456"将得到12.3

  • 文化,它定义了预期的格式。例如,使用fr-FR,解析"12.345"将给出12345,因为分隔符应为,,而不是.

想象一下使用这种方法会多么容易或困难。使用相同的输入,根据调用该方法的时刻,您可能会获得截然不同的结果,因为某些地方某处更改了环境变量或切换了区域性或设置了不同的精度。该方法的不确定性特征将导致更多的错误和更多的调试恶梦。由于某些并行代码正在解析八进制数,因此调用math.parse("12345")并获取5349答案是不好的。

如何解决这种明显损坏的方法?通过引入参照透明性。换句话说,通过摆脱全局状态,并将所有内容移至方法的参数:

decimal math.parse(string t, base=10, precision=20, culture=cultures.en_us)

既然该方法是纯方法,您就知道无论何时调用该方法,对于相同的参数它总是会产生相同的结果。


4
只是一个附录:引用透明适用于语言中的所有表达式,而不仅仅是函数。
gardenhead

3
请注意,透明性是有限的。使packet = socket.recv()参照透明而不是破坏了功能的重点。
2016年

1
应该是culture = cultures.invariant。除非您小心创建了只能在美国使用的软件。
user253751 '16

@immibis:嗯,好问题。解析规则是invariant什么?它们与for相同en_us,在这种情况下,为什么要打扰,或者它们对应于某个其他国家,在这种情况下,用哪一个以及为什么用这个国家代替en_us,或者它们的特定规则始终与任何国​​家都不匹配,这将毫无用处。12,345.67与之间真的没有“真正的答案” 12 345,67:任何“默认规则”都适用于少数国家,而不适用于其他国家。
Arseni Mourzenko '16

3
@ArseniMourzenko通常是“最低公分母”,类似于许多编程语言使用的语法(这也是文化不变的)。12345解析为12345,12 345或者12,34512.345为错误。12.345根据using的编程语言约定,被解析为不变浮点数的结果始终为12.345。作为小数点分隔符。字符串按其Unicode代码点排序,并且区分大小写。等等。
user253751 '16

11

您是否经常在代码中的某个点添加断点并在调试器中运行该应用程序,以便确定正在发生的事情?如果这样做,那很大程度上是因为您在设计中没有使用参照透明性(RT)。因此,必须运行代码来确定其作用。

指向RT的全部要点是代码具有高度确定性,即,对于相同的一组输入,您每次都可以阅读代码并弄清代码的作用。一旦开始添加突变变量(其中一些变量的作用域超出单个函数),就不能只读取代码。这样的代码必须在您的头脑中或在调试器中执行,才能确定其真正的工作方式。

代码阅读和推理越简单,维护和发现错误就越简单,因此为您和您的雇主节省了时间和金钱。


10
“一旦开始添加突变变量,其中一些变量不仅具有单个功能,就不能只读取代码,而必须在头脑中或在调试器中执行它,以弄清其真正的工作原理。 “: 好点子。换句话说,参照透明性不仅意味着一段代码对于相同的输入将始终产生相同的结果,而且意味着产生的结果是该段代码的唯一效果,没有其他隐藏的方面效果就像更改已在另一个模块中定义的某个变量一样。
乔治

这是一个好点。我的代码读取/推理参数更简单确实有问题,因为读取或推理较简单是代码的模糊性和主观性。
Eyal Roth

一旦开始添加变异变量,其中一些变量的作用域超出了单个函数, 但是为什么即使变量作用域对函数而言是局部的,为什么也不鼓励赋值操作呢?
rahulaga_dev

9

人们抛开“更容易思考”一词,但从不解释这意味着什么。考虑以下示例:

result1 = foo("bar", 12)
// 100 lines of code
result2 = foo("bar", 12)

result1result2相同还是不同?没有参照透明性,您将一无所知。您必须实际阅读的内容,foo以确保可能的所有函数foo调用的内容,等等。

人们没有注意到这种负担,因为他们已经习惯了这种负担,但是如果您在纯功能性的环境中工作一两个月,然后回来,您会感觉到的,这是一笔不小的数目

人们需要采取很多防御机制来解决缺乏参照透明性的问题。对于我的小例子,我可能想保留result1在内存中,因为我不知道它是否会改变。然后我有两种状态的代码:result1存储之前和之后。使用参照透明性,只要重新计算不耗时,我就可以轻松地重新计算它。


1
您提到引用透明性使您可以推理对foo()的调用结果,并知道result1result2相同。另一个重要方面是,如果foo("bar", 12)引用透明,则不必问自己此调用是否在其他地方产生了某些效果(设置一些变量?删除了文件?等等)。
Giorgio

我熟悉的唯一“参照完整性”涉及关系数据库。
2016年

1
@Mark这是一个错字。卡尔的意思是参照透明,从他的其余回答中可以明显看出。
Andres F.

6

我会说:参照透明不仅对函数式编程有益,而且对使用函数的每个人都有利,因为它遵循最小惊讶原则。

您有一个功能,可以更好地推断其功能,因为您无需考虑任何外部因素,对于给定的输入,输出将始终相同。即使使用命令式语言,我也会尽可能地遵循这种范例,接下来基本上会自动遵循的是:易于理解的小型函数,而不是我有时运行的令人讨厌的1000多个行函数。

那些大功能确实神奇,我害怕触摸它们,因为它们会以惊人的方式破裂。

因此,纯函数不仅适用于函数式编程,而且适用于每个程序。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.