bob*_*bob 45 haskell functional-programming language-implementation lazy-evaluation purity
我看不到语言的评估策略与其纯度之间的联系。考虑到推文作者的声誉,我肯定忽略了一些东西。也许有人可以阐明一些观点。
lef*_*out 36
你是对的,从现代 POV 来看,这真的没有意义。什么是真正的是,懒惰,由违约将使约侧面effectful代码的噩梦推理,所以lazyness确实需要纯度-而不是周围的其他方式。
需要惰性的是 Haskell 在 1.0-1.2 版本中的方式,在其前身Miranda 之后,在没有 monad 的情况下模拟 IO。没有任何明确的副作用排序概念,可执行程序的类型是
main :: [Response] -> [Request]
Run Code Online (Sandbox Code Playgroud)
对于一个简单的交互式程序,它会像这样工作:main首先会忽略它的输入列表。因此,由于懒惰,该列表中的值实际上不需要存在。同时,它会产生第一个Request值,例如终端提示用户输入内容。然后输入的内容将作为一个Response值返回,现在才真正需要对其进行评估,从而产生一个 newRequest等等。
https://www.haskell.org/definition/haskell-report-1.0.ps.gz
在 1.3 版本中,他们切换到我们今天都知道和喜爱的 monadic-IO 接口,那时懒惰不再是真正必要的了。但在此之前,普遍的看法是,在没有惰性的情况下与现实世界交互的唯一方法是允许副作用函数,因此如果没有惰性,Haskell 就会走上与之前的 Lisp 和 ML 相同的道路。
K. *_*uhr 28
这条推文有两个方面:首先,从技术角度来看,懒惰通常要求纯洁;第二,事实上,从实际情况来看,严可能仍然允许纯度,但在实践中通常不会(即与严格,纯度“走出去窗口”)。
Simon Peyton-Jones 在“A History of Haskell: Being Lazy With Class”一文中解释了这两个方面。关于技术方面,在3.2 Haskell is Pure 部分,他写道(我大胆强调):
懒惰的直接后果是评估顺序是需求驱动的。结果,作为函数调用的结果,可靠地执行输入/输出或其他副作用变得或多或少是不可能的。因此,Haskell 是一种纯粹的语言。
如果你不明白为什么懒惰会使不纯的效果变得不可靠,我敢肯定这是因为你想多了。这是一个说明问题的简单示例。考虑一个假设的不纯函数,它从配置文件中读取一些信息,即一些“基本”配置和一些“扩展”配置,其格式取决于标头中的配置文件版本信息:
getConfig :: Handle -> Config
getConfig h =
let header = readHeader h
basic = readBasicConfig h
extended = readExtendedConfig (headerVersion header) h
in Config basic extended
Run Code Online (Sandbox Code Playgroud)
其中readHeader、readBasicConfig和readExtendedConfig都是不纯函数,它们从文件中顺序读取字节(即使用典型的基于文件指针的顺序读取)并将它们解析为适当的数据结构。
在惰性语言中,此功能可能无法按预期工作。如果header, basic, 和extended变量值都被惰性求值,那么如果调用者强制basic先加extended,则效果将按顺序调用readBasic,readHeader, readExtendedConfig; 而如果调用者extended首先强制执行basic,则效果将按顺序调用readHeader,readExtendedConfig, readBasic。在任一情况下,打算由一个函数解析的字节将被另一个函数解析。
而且,这些评估顺序是粗略的过度简化,它们假设子函数的效果是“原子的”,并且readExtendedConfig可靠地强制版本参数对extended. 如果没有,这取决于哪部分的basic与extended被强制,在(子)效果的顺序readBasic,readExtendedConfig以及readHeader可以被重新排序和/或混合。
您可以通过禁止顺序文件访问来解决此特定限制(尽管这会带来一些重大成本!),但类似的不可预测的乱序效果执行将导致其他 I/O 操作出现问题(我们如何确保文件更新函数在截断文件以进行更新之前读取旧内容?),可变变量(该锁定变量究竟何时增加?)等。
关于实际方面(再次强调我的大胆强调),SPJ 写道:
一旦我们致力于一种懒惰的语言,一种纯粹的语言是不可避免的。反之则不然,但值得注意的是,在实践中,大多数纯编程语言也是懒惰的。为什么?因为在按值调用的语言中,无论是否具有功能性,在“函数”中允许不受限制的副作用的诱惑几乎是不可抗拒的。
...
因此,回想起来,也许懒惰的最大单一好处不是懒惰本身,而是懒惰使我们保持纯净,从而激发了大量关于 monad 和封装状态的富有成效的工作。
在他的推文中,我相信 Hutton 指的不是懒惰导致纯度的技术后果,而是严格诱使语言设计者放松纯度的实际后果,“只是在这种情况下,特殊情况”,之后纯度很快就会消失窗户。
Li-*_*Xia 17
其他答案给出了最有可能是该评论所指的历史背景。我认为这种联系更加深入。
Eager 语言,即使是那些自称“纯”的语言,也没有像 Haskell 那样强烈的参照透明性:
let f = E in
\x -> f x
Run Code Online (Sandbox Code Playgroud)
不等于
\x -> E x
Run Code Online (Sandbox Code Playgroud)
如果前一个表达式被急切地求值并且求值E发散。
Eager 语言需要区分值和计算:变量仅替换为值,但表达式代表计算,这就是为什么上述“明显”简化let无效的原因。一个表达式比它所表示的值更多,这正是语言有效的含义。在这个非常技术性的意义上,像 Purescript(我在这个领域能想到的第一个例子)这样的热切语言并不纯粹。
当我们忽略非终止和求值顺序时,Purescript 是纯的,这几乎是每个程序员所做的,因此值得称赞。
相比之下,在惰性语言中,值和计算之间的区别是模糊的。一切都表示一个值,甚至是“底部”的非终止表达式。您可以随意替换,而不必担心表达式的作用。在我看来,这就是纯洁点。
有人可能会争辩说,实际上,我们也可以说,在一种渴望语言中的发散表达式表示底部,而它只是let表示一个严格的函数。老实说,后验可能是一个很好的解释,但除了 Haskellers 和编程语言恐怖分子之外,没有人会这么认为。