逃逸Monad IO

4

我最喜欢Haskell的一点就是编译器能够通过函数签名在IO单子中定位副作用。然而,通过导入2个GHC基元,似乎很容易绕过这种类型检查:

{-# LANGUAGE MagicHash #-}

import GHC.Magic(runRW#)
import GHC.Types(IO(..))

hiddenPrint :: ()
hiddenPrint = case putStrLn "Hello !" of
  IO sideEffect -> case runRW# sideEffect of
    _ -> ()

hiddenPrint 的类型是 unit,但当调用该函数时会触发副作用(它会输出 Hello)。除了不信任任何人导入 GHC 原语外,是否有办法禁止这些隐藏的 IOs?


7
我认为IO(以及类似的安全功能)的实用性应该以“如果你不想破坏它,那么让它工作有多容易”来衡量,而不是以“如果你想破坏它,那么让它变得多容易被破坏”来衡量。 - András Kovács
2
另外,导入 System.IO.Unsafe 并使用 unsafePerformIO 似乎更容易 ;). 虽然很容易绕过,但是 不安全的. - Zeta
unsafeDupablePerformIO (IO m) = case runRW# m of (# _, a #) -> a 这段代码完全相同,我之前不知道这个模块 :) 当我阅读简化器的核心时,偶然发现了 runRW# - V. Semeria
使用realWorld#而非runRW#更不安全。 - dfeuer
2个回答

10

这就是 Safe Haskell 的目的。如果你在源文件顶部添加 {-# language Safe #-},那么你只能导入被推断为安全的模块或标记为 {-# language Trustworthy #-} 的模块。这也对重叠实例施加了一些温和的限制。


3
有许多方法可以在Haskell中破坏纯度。然而,您必须费尽心思才能找到它们。以下是其中的一些:
- 导入GHC内部模块以访问低级原语 - 使用FFI将C函数导入为纯函数,即使它不是纯函数 - 使用unsafePerformIO - 使用unsafeCoerce - 使用Ptr a并解引用不指向有效数据的指针 - 声明自己的Typeable实例,并撒谎以便滥用cast。(在最近的GHC版本中不再可能) - 使用不安全的数组操作
所有这些,以及其他一些,都不应该经常使用。它们存在的意义是,如果您非常确定,可以告诉编译器“我知道我在做什么,请不要阻挡我”。这样做,您就承担了证明的重担 - 证明您所做的事情是安全的。

1
在最近的 GHC 版本中,你不能定义自己的 Typeable 实例。你能做的最接近的方式是使用类似 Dict (Typeable a)unsafeCoerce,但这只会产生局部效果。 - dfeuer
@dfeuer 我怀疑自从实现了自动派生后,这已经不再可能了。感谢您的澄清。 - chi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接