我正在阅读 Haskell 教程(Learn You a Haskell),其中作者说惰性与引用透明性相得益彰。经过更多的阅读和一些搜索,我仍然不明白为什么。请注意,我确实了解引用透明性和惰性的优点,但让我感到困扰的是它们在一起。
两者结合有什么特别的好处吗?
或者也许作者只是想说他们都很好,但表达得模棱两可?
我正在阅读 Haskell 教程(Learn You a Haskell),其中作者说惰性与引用透明性相得益彰。经过更多的阅读和一些搜索,我仍然不明白为什么。请注意,我确实了解引用透明性和惰性的优点,但让我感到困扰的是它们在一起。
两者结合有什么特别的好处吗?
或者也许作者只是想说他们都很好,但表达得模棱两可?
这真的很容易。非严格(例如惰性)评估意味着可以推迟任务。但是为了推迟某些事情,你最好确保以后得到与现在相同的结果,这就是参照透明性。考虑这个命令式 Java 代码:
long start = System.currentTimeMillis(); //get the start time
runBenchmarkFunction();
System.out.println("Run took " + (System.currentTimeMillis() - start) + " ms");
现在懒惰的语言会推迟第一行的评估,因为 start 只需要在第三行。所以结果将是 0(或非常接近它)。可能这不是你想要的。这个麻烦的原因是 System.currentTimeMillis不是引用透明的。如果它是“数学意义上”的函数,如 sin 或 ln,在这种情况下你不会有任何问题,它们是引用透明的。
参照透明意味着在给定相同输入的情况下,函数将始终返回相同的输出。因此,如果函数是惰性的还是严格的,则无关紧要。惰性函数将在未来某个未知时间计算其输出,但由于引用透明性,您可以保证对于给定输入,输出始终相同。
所以在某种程度上,引用透明性保证了惰性函数的正确性。
考虑这个 Python 代码,其中一个生成器用于延迟计算无限序列。由于它使用全局状态,它没有引用透明性,因此生成器的调用者不能确定他们得到的结果没有受到其他事件的影响。
foo = 0
def foo_sequence():
global foo
while True:
foo += 1
yield foo
>>> generator = foo_sequence()
>>> generator.next()
1
>>> generator.next()
2
>>> foo = 5
>>> generator.next()
6
在这种情况下,调用者更愿意以原子方式生成整个序列,以防止此类事件发生。因此,缺乏引用透明度(在这个人为的例子中)使得懒惰没有吸引力。