有一些关于将其作为部门范围内的禁止使用unsafePerformIO
及其类似物的政策的讨论。就个人而言,我并不介意,因为我一直认为,如果我发现自己想要使用它,通常意味着我需要重新考虑我的方法。
这个限制听起来合理吗?我似乎记得在某处读过它主要是为 FFI 包含的,但我现在不记得我在哪里读到了。
编辑:好的,那是我的错。它不会在合理需要的地方受到限制,即。FFI。该政策的重点更多地是为了阻止懒惰和代码异味。
有一些关于将其作为部门范围内的禁止使用unsafePerformIO
及其类似物的政策的讨论。就个人而言,我并不介意,因为我一直认为,如果我发现自己想要使用它,通常意味着我需要重新考虑我的方法。
这个限制听起来合理吗?我似乎记得在某处读过它主要是为 FFI 包含的,但我现在不记得我在哪里读到了。
编辑:好的,那是我的错。它不会在合理需要的地方受到限制,即。FFI。该政策的重点更多地是为了阻止懒惰和代码异味。
许多核心库喜欢在底层ByteString
使用unsafePerformIO
,例如自定义内存分配。
当您使用这样的库时,您相信库作者已经证明了他们导出的 API 的引用透明性,并且记录了用户的任何必要先决条件。您的部门应该制定政策和审查流程,以便在内部做出类似的保证,而不是一揽子禁令。
好吧,. _ unsafePerformIO
这不仅仅是为了装饰,也不是为了测试你的美德。然而,这些用途都不涉及为日常代码添加有意义的副作用。以下是一些可能合理的使用示例,但存在不同程度的怀疑:
包装一个内部不纯的函数,但没有外部可观察到的副作用。这与ST
monad 的基本思想相同,只是这里的责任在于程序员证明杂质不会“泄漏”。
以某种受限的方式伪装一个故意不纯的函数。例如,只写杂质看起来与“从内部”的总纯度相同,因为无法观察产生的输出。这对于某些类型的日志记录或调试很有用,在这些情况下,您明确不IO
希望monad所需的一致性和明确定义的顺序。这方面的一个例子是Debug.Trace.trace
,我有时将其称为unsafePerformPrintfDebugging
。
对纯计算的自省,产生纯结果。一个经典的例子是类似明确的选择运算符,它可以并行运行两个等效的纯函数,以便更快地得到答案。
内部不可观察的引用透明性破坏,例如在初始化数据时引入不确定性。只要每个不纯函数只评估一次,在程序的任何一次运行期间都将有效地保持引用透明性,即使使用相同参数调用的同一个伪纯函数在不同运行时给出不同的结果。
关于以上所有需要注意的重要一点是,由此产生的杂质受到严格控制并在范围内受到限制。给定一个比通用 monad 更细粒度的副作用控制系统IO
,这些都将是切掉半纯度位的明显候选者,就像前面提到的ST
monad 中的受控可变状态一样。
后记:unsafePerformIO
如果正在考虑对任何非必需使用的强硬立场,我强烈建议将禁令扩大到包括unsafeInterleaveIO
和允许观察其行为的任何功能。如果你问我,它至少和unsafePerformIO
我上面列出的一些例子一样粗略。
unsafePerformIO 是 IO monad 的 runST。有时是必不可少的。但是,与 runST 不同,编译器无法检查您是否保留了引用透明度。
所以如果你使用它,程序员就有负担解释为什么使用是安全的。它不应该被禁止,它应该伴随着证据。
在“应用程序”代码中取缔unsafePerformIO
是一个绝妙的主意。在我看来,在普通代码中没有任何借口,unsafePerformIO
根据我的经验,它是不需要的。它真的不是语言的一部分,所以如果你使用它,你就不再真正在 Haskell 中编程了。你怎么知道它甚至意味着什么?
另一方面,unsafePerformIO
如果您知道自己在做什么,那么在 FFI 绑定中使用是合理的。
取缔 unsafePerformIO 是一个糟糕的想法,因为它有效地将代码锁定在 IO monad 中:例如,ac 库绑定几乎总是在 IO monad 中 - 但是,使用 unsafePerformIO 可以在其之上构建更高级别的纯函数库.
可以说,unsafePerformIO 反映了个人计算机的高度状态模型和 haskell 的纯无状态模型之间的折衷;从计算机的角度来看,即使是函数调用也是有状态的,因为它需要将参数推入堆栈,弄乱寄存器等,但使用是基于这些操作实际上是按功能组合的知识。