Haskell通常被引用为纯函数式语言的典范。鉴于存在System.IO.Unsafe.unsafePerformIO
,这该如何证明?
编辑:我原以为“纯函数式”意味着不可能将不纯的代码引入程序的函数式部分。
Haskell通常被引用为纯函数式语言的典范。鉴于存在System.IO.Unsafe.unsafePerformIO
,这该如何证明?
编辑:我原以为“纯函数式”意味着不可能将不纯的代码引入程序的函数式部分。
我们称之为Haskell的语言
unsafePerformIO
是Foreign Function Interface规范的一部分,而不是核心的Haskell 98规范。它可以用于执行本地副作用,以便在暴露纯函数接口时隐藏效果。也就是说,我们使用它来隐藏类型检查器无法为我们隐藏的效果(不像ST monad,后者通过静态保证隐藏效果)。
为了准确说明我们所称的“Haskell”具有多重语言特性,请参考下面的图像。每个环都对应一组特定的计算特性,按安全性排序,并且其面积与表现力相关(即如果您具备该特性,则可以编写的程序数量)。
已知的Haskell 98语言被指定为完全和部分函数,位于中间位置。只允许总函数的Agda(或 Epigram )甚至更不具有表现力,但更“纯”和更安全。虽然今天我们使用的Haskell包括FFI中的所有内容,其中包括unsafePerformIO。也就是说,您可以在现代Haskell中编写任何东西,但如果使用来自外部环的内容,则更难建立由内部环简化的安全和保障保证。
因此,Haskell程序通常不是完全由可透明引用代码构建的,但它仍然是唯一一种默认情况下是纯的中度常见语言。
unsafePerformIO
并不是Haskell的一部分,就像垃圾回收器或运行时系统不是Haskell的一部分一样。系统中存在unsafePerformIO
是为了让构建系统的人可以在非常有影响的硬件上创建纯函数式抽象。所有真正的语言都有漏洞,让系统构建者以比下降到C代码或汇编代码更有效的方式完成任务。IO
单子适应Haskell的整体情况,我认为最容易思考Haskell的方法是它是一种描述有影响计算的纯语言。当所描述的计算是main
时,运行时系统会忠实地执行这些影响。
unsafePerformIO
是以不安全的方式获取效果的一种方式;其中“不安全”意味着“程序员必须保证安全性”——编译器没有检查任何内容。如果您是一个明智的程序员,并愿意承担重大的证明义务,您可以使用unsafePerformIO
。但此时,您不再使用Haskell进行编程;您正在使用类似Haskell的不安全语言进行编程。这种语言/实现是纯函数式的。它包括一些“逃生口”,如果你不想使用,可以不用。
我认为unsafePerformIO并不意味着Haskell变得不纯。您可以从不纯的函数创建纯(引用透明)函数。
考虑跳表。为了使其正常工作,它需要访问RNG(一个不纯的函数),但这并不使数据结构变得不纯。如果您添加一个项目,然后将其转换为列表,则给定您添加的项目,每次都会返回相同的列表。
因此,我认为应该将unsafePerformIO视为promisePureIO。这是一种函数,意味着具有副作用并因此被类型系统标记为不纯的函数可以通过类型系统被确认为引用透明。
我知道为了实现这一点,您必须对纯的定义进行略微放宽。即纯函数是引用透明的,从不因副作用(如打印)而被调用。
安全的Haskell是GHC的最新扩展,为这个问题提供了一个新的答案。unsafePerformIO
是GHC Haskell的一部分,但不是安全方言的一部分。
unsafePerformIO
只应用于构建引用透明函数,例如记忆化。在这些情况下,包的作者将其标记为“可信任”。安全模块只能导入安全和可信任的模块;它不能导入不安全的模块。
更多信息:GHC手册, 安全的Haskell论文
不幸的是,编程语言必须处理一些现实世界的工作,这意味着需要与外部环境进行交互。
好消息是,您可以(并且应该)将这种“过时”的代码的使用限制在程序中少数特定、有良好文档记录的部分。
我有一种感觉,我将因为我即将说的话而变得非常不受欢迎,但我觉得我必须回应这里提出的(在我看来是误导性的)信息。
虽然unsafePerformIO确实作为FFI附录的一部分被正式添加到语言中,但其原因主要是历史性的而不是逻辑性的。它在Haskell拥有FFI之前就已经非正式存在并广泛使用。它从未正式成为Haskell主要标准的一部分,因为正如你所观察到的那样,它只是太过尴尬了。我想希望是它在未来某个时候会消失。好吧,我认为这不会发生,也不会消失。
FFI附录的开发为unsafePerformIO提供了一个方便的借口,使其能够被悄悄地加入到官方语言标准中。与添加调用外部(即C)代码的能力相比(在这种情况下,无法静态确保纯度和类型安全),它可能看起来并不那么糟糕。出于政治原因,将它放在这里也非常方便。它培养了这样一个神话:如果没有所有那些肮脏的“设计不良”的C、操作系统或硬件等等,Haskell就会是纯的。当然,unsafePerformIO经常与FFI相关的代码一起使用,但这样做的原因通常更多地与FFI和Haskell本身的设计不良有关,而不是与Haskell试图接口的任何外部事物的设计不良有关。我实在没有时间、空间或倾向去深入讲解什么是“unsafePerformIO hack”以及为什么真正的IO库需要它,但底线是,那些致力于Haskell IO基础架构的人通常“进退两难”。他们可以提供天生安全的API,但无法在Haskell中进行安全实现,或者他们可以提供固有不安全的API,但可以安全地实现。但他们很少能同时在API设计和实现中提供安全性。鉴于“unsafePerformIO hack”在现实世界代码(包括Haskell标准库)中出现的令人沮丧的频率,似乎大多数人选择前者作为两种恶中较轻的一种,并希望编译器不会在内联、CSE或任何其他转换中搞砸事情。
我真希望这一切不是这样。不幸的是,它就是这样。