懒惰和纯洁之间有什么联系?

bob*_*bob 45 haskell functional-programming language-implementation lazy-evaluation purity

懒惰是保持 Haskell 纯净的原因。如果是严格的,纯洁很快就会消失。

我看不到语言的评估策略与其纯度之间的联系。考虑到推文作者的声誉,我肯定忽略了一些东西。也许有人可以阐明一些观点。

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 相同的道路。

  • 我不确定第一段在说什么。懒惰 => 纯粹,因此,如果我们致力于让 Haskell 变得懒惰,我们就致力于使其变得纯粹。如果 Haskell 是严格的,那么就没有“要求”Haskell 是不纯的,但语言实现者可能会屈服于诱惑并开始允许不纯,因为这是可能的。该声明是对我们人类的评论,而不仅仅是对语言本身的设计。 (9认同)
  • @HTNW 语言是由机器人设计和使用的吗? (2认同)

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)

其中readHeaderreadBasicConfigreadExtendedConfig都是不纯函数,它们从文件中顺序读取字节(即使用典型的基于文件指针的顺序读取)并将它们解析为适当的数据结构。

在惰性语言中,此功能可能无法按预期工作。如果header, basic, 和extended变量值都被惰性求值,那么如果调用者强制basic先加extended,则效果将按顺序调用readBasicreadHeader, readExtendedConfig; 而如果调用者extended首先强制执行basic,则效果将按顺序调用readHeaderreadExtendedConfig, readBasic。在任一情况下,打算由一个函数解析的字节将被另一个函数解析。

而且,这些评估顺序是粗略的过度简化,它们假设子函数的效果是“原子的”,并且readExtendedConfig可靠地强制版本参数对extended. 如果没有,这取决于哪部分basicextended被强制,在(子)效果的顺序readBasicreadExtendedConfig以及readHeader可以被重新排序和/或混合。

您可以通过禁止顺序文件访问来解决此特定限制(尽管这会带来一些重大成本!),但类似的不可预测的乱序效果执行将导致其他 I/O 操作出现问题(我们如何确保文件更新函数在截断文件以进行更新之前读取旧内容?),可变变量(该锁定变量究竟何时增加?)等。

关于实际方面(再次强调我的大胆强调),SPJ 写道:

一旦我们致力于一种懒惰的语言,一种纯粹的语言是不可避免的。反之则不然,但值得注意的是,在实践中,大多数纯编程语言也是懒惰的。为什么?因为在按值调用的语言中,无论是否具有功能性,在“函数”中允许不受限制的副作用的诱惑几乎是不可抗拒的。

...

因此,回想起来,也许懒惰的最大单一好处不是懒惰本身,而是懒惰使我们保持纯净,从而激发了大量关于 monad 和封装状态的富有成效的工作。

在他的推文中,我相信 Hutton 指的不是懒惰导致纯度的技术后果,而是严格诱使语言设计者放松纯度的实际后果,“只是在这种情况下,特殊情况”,之后纯度很快就会消失窗户。

  • 这。Haskell 实现者甚至屈服于诱惑,并通过“unsafePerformIO”在计算中允许不受限制的副作用 - 但懒惰使每个人都远离实际利用它来构建不纯的程序,因为他们不能依赖评估顺序。 (8认同)

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 和编程语言恐怖分子之外,没有人会这么认为。

  • 我认为在你的最后一行中,你实际上指的是“编程语言理论家”,但我可以想到一些我可能称之为“编程语言恐怖分子”的人! (13认同)
  • 你用你的例子提出了一个有效的观点,但实际的相关性是有限的,因为尽管很懒,Haskell实际上也有几乎相同的问题:在`let f=E in \x->fx`中,`E`只会是在 `\x->E x` 中计算一次,它将在每次函数调用时一遍又一遍地计算。它不像严格情况下的 ⊥ 那样明显,但对于程序员来说仍然非常重要。实际上,为每次出现的情况选择更好的编写方式并不是什么大问题。 (2认同)