在编程中,引用透明的好处是什么?
RT是功能性和命令性范式之间的主要区别之一,并且功能性范式的倡导者经常将RT用作与命令性范式相比的明显优势。但是,这些拥护者们在他们的所有努力中,从来没有解释过为什么这对我作为程序员来说是有益的。
当然,他们会对它们的“纯”和“优雅”有自己的学术解释,但是它如何使它比不太“纯”的代码更好呢?它对我的日常编程有什么好处?
注意: 这不是“ 什么是参照透明性”的副本。 后者地址的话题是什么是RT的问题,而这个问题解决了它的好处(可能不是那么直观)。
在编程中,引用透明的好处是什么?
RT是功能性和命令性范式之间的主要区别之一,并且功能性范式的倡导者经常将RT用作与命令性范式相比的明显优势。但是,这些拥护者们在他们的所有努力中,从来没有解释过为什么这对我作为程序员来说是有益的。
当然,他们会对它们的“纯”和“优雅”有自己的学术解释,但是它如何使它比不太“纯”的代码更好呢?它对我的日常编程有什么好处?
注意: 这不是“ 什么是参照透明性”的副本。 后者地址的话题是什么是RT的问题,而这个问题解决了它的好处(可能不是那么直观)。
Answers:
好处是纯函数使您的代码更易于推理。换句话说,副作用会增加代码的复杂性。
以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)
既然该方法是纯方法,您就知道无论何时调用该方法,对于相同的参数它总是会产生相同的结果。
packet = socket.recv()
参照透明而不是破坏了功能的重点。
invariant
什么?它们与for相同en_us
,在这种情况下,为什么要打扰,或者它们对应于某个其他国家,在这种情况下,用哪一个以及为什么用这个国家代替en_us
,或者它们的特定规则始终与任何国家都不匹配,这将毫无用处。12,345.67
与之间真的没有“真正的答案” 12 345,67
:任何“默认规则”都适用于少数国家,而不适用于其他国家。
12345
解析为12345,12 345
或者12,345
或12.345
为错误。12.345
根据using的编程语言约定,被解析为不变浮点数的结果始终为12.345。作为小数点分隔符。字符串按其Unicode代码点排序,并且区分大小写。等等。
您是否经常在代码中的某个点添加断点并在调试器中运行该应用程序,以便确定正在发生的事情?如果这样做,那很大程度上是因为您在设计中没有使用参照透明性(RT)。因此,必须运行代码来确定其作用。
指向RT的全部要点是代码具有高度确定性,即,对于相同的一组输入,您每次都可以阅读代码并弄清代码的作用。一旦开始添加突变变量(其中一些变量的作用域超出单个函数),就不能只读取代码。这样的代码必须在您的头脑中或在调试器中执行,才能确定其真正的工作方式。
代码阅读和推理越简单,维护和发现错误就越简单,因此为您和您的雇主节省了时间和金钱。
人们抛开“更容易思考”一词,但从不解释这意味着什么。考虑以下示例:
result1 = foo("bar", 12)
// 100 lines of code
result2 = foo("bar", 12)
是result1
和result2
相同还是不同?没有参照透明性,您将一无所知。您必须实际阅读的内容,foo
以确保可能的所有函数foo
调用的内容,等等。
人们没有注意到这种负担,因为他们已经习惯了这种负担,但是如果您在纯功能性的环境中工作一两个月,然后回来,您会感觉到的,这是一笔不小的数目。
人们需要采取很多防御机制来解决缺乏参照透明性的问题。对于我的小例子,我可能想保留result1
在内存中,因为我不知道它是否会改变。然后我有两种状态的代码:result1
存储之前和之后。使用参照透明性,只要重新计算不耗时,我就可以轻松地重新计算它。
result1
和result2
相同。另一个重要方面是,如果foo("bar", 12)
引用透明,则不必问自己此调用是否在其他地方产生了某些效果(设置一些变量?删除了文件?等等)。