问题描述
Laziness is what kept Haskell pure. If it had been strict,purity would soon go out the window.
我看不到一种语言的评估策略与其纯正性之间的联系。考虑到推文作者的声誉,我当然可以忽略一些东西。也许有人可以阐明一些想法。
解决方法
您是对的,从现代POV来看,这没有任何意义。 的真实含义是,默认情况下的惰性会使推理副作用代码成为一场噩梦,因此惰性确实需要纯洁,但反之则不需要。
尽管确实需要懒惰,但是Haskell在其前身Miranda之后的1.0–1.2版中,是在没有Monad的情况下模拟IO的。缺少任何明确的副作用排序概念,可执行程序的类型为
main :: [Response] -> [Request]
对于一个简单的交互式程序,的工作原理如下:main
首先只是忽略其输入列表。因此,由于懒惰,此时该列表中的值实际上并不需要存在。同时,它会产生第一个Request
值,例如终端提示用户输入内容。然后,键入的内容将返回为Response
值,直到现在才真正需要对其进行评估,从而产生新的Request
等,等等。
https://www.haskell.org/definition/haskell-report-1.0.ps.gz
然后在1.3版中,他们切换到了我们今天都知道和喜欢的monadic-IO界面,到那时,不再真正需要懒惰了。但是在此之前,普遍的看法是,在没有懒惰的情况下与现实世界进行交互的唯一方法是允许具有副作用的功能,因此,声明如果没有懒惰,Haskell将走上与Lisp和ML相同的道路。
,此推文有两个方面:第一,从技术角度来看,懒惰通常要求纯洁;第二,从实践的角度来看,严格性仍然可以允许纯净,但实际上通常不允许(例如,严格性而言,纯净会“消失”)。
西蒙·佩顿·琼斯(Simon Peyton-Jones)在论文"A History of Haskell: Being Lazy With Class"中解释了这两个方面。关于技术方面,他在 3.2 Haskell是Pure 部分中写道(我强调 Bold ):
懒惰的直接后果是评估顺序受需求驱动。结果,由于功能调用,可靠地执行输入/输出或其他副作用变得越来越了。因此,Haskell是一种纯语言。
如果您看不到为什么懒惰会使不纯的效果变得不可靠,那么我相信这是因为您过度考虑了它。这是说明问题的简单示例。考虑一个假想的不纯函数,该函数从配置文件中读取一些信息,即一些“基本”配置和一些“扩展”配置,其格式取决于标题中的配置文件版本信息:
getConfig :: Handle -> Config
getConfig h =
let header = readHeader h
basic = readBasicConfig h
extended = readExtendedConfig (headerVersion header) h
in Config basic extended
其中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写道:
一旦我们致力于一种 lazy 语言,就无法避免使用 pure 语言。相反,事实并非如此,但是值得注意的是,实际上大多数纯编程语言都是惰性的。为什么?因为在按值通话语言中,无论功能是否有效,在“功能”内部允许不受限制的副作用的诱惑几乎是无法抗拒的。
...
因此,回想起来,懒惰的最大单一好处可能不是本身懒惰,而是懒惰使我们保持纯洁,从而激发了很多关于单子和封装状态的生产性工作。
在他的推文中,我相信Hutton所指的并不是懒惰导致纯净的技术后果,而是严格的实际后果,诱使语言设计师“仅在这种特殊情况下”放松纯净,迅速离开窗户。
,其他答案给出的历史背景很可能是该评论所指的内容。我认为连接更加深入。
即使是自称“纯”的语言,也没有像Haskell那样强烈的参照透明性:
let f = E in
\x -> f x
不等于
\x -> E x
如果对前一个表达式进行了急切的求值,而对E
的求值则有所不同。
早期的语言要求在值和计算之间进行区分:变量仅用值代替,而表达式代表计算,这就是为什么上述let
的“明显”简化无效的原因。一个表达比它所表示的值还多,这正是一种语言有效的含义。从这个非常技术的角度来看,像Purescript这样的急切语言(我在这个领域中想到的第一个示例)并不是纯净的。
当我们忽略非终止和评估顺序时,Purescript是纯正的,几乎每个程序员都这样做,因此值得赞赏。
相反,在惰性语言中,值和计算之间的区别是模糊的。一切都表示一个值,即使是非终止表达式,也都是“底”。 您可以替换所有想要的内容而不必担心做什么。我认为那是纯洁的要点。
有人可能会说,实际上,我们也可以说,急切的语言中的歧义表达表示 bottom ,而let
只是表示严格的函数。坦白地说,这可能是对后验的一个很好的解释,但是除了Haskellers和编程语言恐怖分子之外,没有人这么认为。