unsafeInterleaveIO 什么时候不安全?

2024-05-03

与其他不安全*操作不同,文档 http://hackage.haskell.org/packages/archive/base/latest/doc/html/System-IO-Unsafe.html#v:unsafeInterleaveIO for unsafeInterleaveIO不太清楚它可能存在的陷阱。那么具体什么时候不安全呢?我想知道并行/并发和单线程使用的条件。

更具体地说,以下代码中的两个函数在语义上是否等效?如果没有,何时以及如何?


joinIO :: IO a -> (a -> IO b) -> IO b
joinIO  a f = do !x  <- a
                    !x'  <- f x
                    return x'

joinIO':: IO a -> (a -> IO b) -> IO b
joinIO' a f = do !x  <- unsafeInterleaveIO a
                    !x' <- unsafeInterleaveIO $ f x
                    return x'

以下是我在实践中如何使用它:


data LIO a = LIO {runLIO :: IO a}

instance Functor LIO where
  fmap f (LIO a) = LIO (fmap f a)

instance Monad LIO where
  return x = LIO $ return x
  a >>= f  = LIO $ lazily a >>= lazily . f
    where
      lazily = unsafeInterleaveIO . runLIO

iterateLIO :: (a -> LIO a) -> a -> LIO [a]
iterateLIO f x = do
  x' <- f x
  xs <- iterateLIO f x'  -- IO monad would diverge here
  return $ x:xs

limitLIO :: (a -> LIO a) -> a -> (a -> a -> Bool) -> LIO a
limitLIO f a converged = do
  xs <- iterateLIO f a
  return . snd . head . filter (uncurry converged) $ zip xs (tail xs)

root2 = runLIO $ limitLIO newtonLIO 1 converged
  where
    newtonLIO x = do () <- LIO $ print x
                           LIO $ print "lazy io"
                           return $ x - f x / f' x
    f  x = x^2 -2
    f' x = 2 * x
    converged x x' = abs (x-x') < 1E-15

尽管我宁愿避免在严肃的应用程序中使用此代码,因为它的可怕性unsafe*在决定“收敛”的含义时,我至少可以比使用更严格的 IO monad 更懒,从而导致(我认为)更惯用的 Haskell。这就提出了另一个问题:为什么它不是 Haskell(或 GHC?)IO monad 的默认语义?我听说过一些惰性 IO 的资源管理问题(GHC 仅通过一小部分固定命令提供),但通常给出的示例有点类似于损坏的 makefile:资源 X 依赖于资源 Y,但如果失败为了指定依赖关系,您会得到 X 的未定义状态。惰性 IO 真的是这个问题的罪魁祸首吗? (另一方面,如果上述代码中存在微妙的并发错误(例如死锁),我会将其视为更根本的问题。)

Update

阅读了 Ben 和 Dietrich 的回答以及他下面的评论,我简要浏览了 ghc 源代码,了解 IO monad 在 GHC 中是如何实现的。在这里,我总结了我的一些发现。

  1. GHC 将 Haskell 实现为一种不纯粹的、非引用透明的语言。 GHC 的运行时通过连续评估具有副作用的不纯函数来运行,就像任何其他函数式语言一样。这就是评估顺序很重要的原因。

  2. unsafeInterleaveIO是不安全的,因为即使在单线程程序中,它也会通过暴露 GHC Haskell 的(通常)隐藏杂质而引入任何类型的并发错误。 (iteratee似乎是一个很好且优雅的解决方案,我一定会学习如何使用它。)

  3. IO monad 必须严格,因为安全、惰性的 IO monad 需要真实世界的精确(提升)表示,这似乎是不可能的。

  4. 这不仅仅是 IO monadunsafe不安全的函数。整个 Haskell(由 GHC 实现)可能是不安全的,并且(GHC 的)Haskell 中的“纯”函数只是按照惯例和人们的善意而纯的。类型永远不能证明纯度。

为了看到这一点,我演示了 GHC 的 Haskell 是如何not引用透明,无论 IO monad 如何,无论unsafe*功能等


-- An evil example of a function whose result depends on a particular
-- evaluation order without reference to unsafe* functions  or even
-- the IO monad.

{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE BangPatterns #-}
import GHC.Prim

f :: Int -> Int
f x = let v = myVar 1
          -- removing the strictness in the following changes the result
          !x' = h v x
      in g v x'

g :: MutVar# RealWorld Int -> Int -> Int
g v x = let !y = addMyVar v 1
        in x * y

h :: MutVar# RealWorld Int -> Int -> Int
h v x = let !y = readMyVar v
        in x + y

myVar :: Int -> MutVar# (RealWorld) Int
myVar x =
    case newMutVar# x realWorld# of
         (# _ , v #) -> v

readMyVar :: MutVar# (RealWorld) Int -> Int
readMyVar v =
    case readMutVar# v realWorld# of
         (# _ , x #) -> x

addMyVar :: MutVar# (RealWorld) Int -> Int -> Int
addMyVar v x =
  case readMutVar# v realWorld# of
    (# s , y #) ->
      case writeMutVar# v (x+y) s of
        s' -> x + y

main =  print $ f 1

为了方便参考,我收集了一些相关的定义 对于 GHC 实现的 IO monad。 (下面的所有路径都是相对于 ghc 源存储库的顶级目录。)


--  Firstly, according to "libraries/base/GHC/IO.hs",
{-
The IO Monad is just an instance of the ST monad, where the state is
the real world.  We use the exception mechanism (in GHC.Exception) to
implement IO exceptions.
...
-}

-- And indeed in "libraries/ghc-prim/GHC/Types.hs", We have
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))

-- And in "libraries/base/GHC/Base.lhs", we have the Monad instance for IO:
data RealWorld
instance  Functor IO where
   fmap f x = x >>= (return . f)

instance  Monad IO  where
    m >> k    = m >>= \ _ -> k
    return    = returnIO
    (>>=)     = bindIO
    fail s    = failIO s

returnIO :: a -> IO a
returnIO x = IO $ \ s -> (# s, x #)

bindIO :: IO a -> (a -> IO b) -> IO b
bindIO (IO m) k = IO $ \ s -> case m s of (# new_s, a #) -> unIO (k a) new_s

unIO :: IO a -> (State# RealWorld -> (# State# RealWorld, a #))
unIO (IO a) = a

-- Many of the unsafe* functions are defined in "libraries/base/GHC/IO.hs":
unsafePerformIO :: IO a -> a
unsafePerformIO m = unsafeDupablePerformIO (noDuplicate >> m)

unsafeDupablePerformIO  :: IO a -> a
unsafeDupablePerformIO (IO m) = lazy (case m realWorld# of (# _, r #) -> r)

unsafeInterleaveIO :: IO a -> IO a
unsafeInterleaveIO m = unsafeDupableInterleaveIO (noDuplicate >> m)

unsafeDupableInterleaveIO :: IO a -> IO a
unsafeDupableInterleaveIO (IO m)
  = IO ( \ s -> let
                   r = case m s of (# _, res #) -> res
                in
                (# s, r #))

noDuplicate :: IO ()
noDuplicate = IO $ \s -> case noDuplicate# s of s' -> (# s', () #)

-- The auto-generated file "libraries/ghc-prim/dist-install/build/autogen/GHC/Prim.hs"
-- list types of all the primitive impure functions. For example,
data MutVar# s a
data State# s

newMutVar# :: a -> State# s -> (# State# s,MutVar# s a #)
-- The actual implementations are found in "rts/PrimOps.cmm".

因此,例如,忽略构造函数并假设引用透明, 我们有


unsafeDupableInterleaveIO m >>= f
==>  (let u = unsafeDupableInterleaveIO)
u m >>= f
==> (definition of (>>=) and ignore the constructor)
\s -> case u m s of
        (# s',a' #) -> f a' s'
==> (definition of u and let snd# x = case x of (# _,r #) -> r)
\s -> case (let r = snd# (m s)
            in (# s,r #)
           ) of
       (# s',a' #) -> f a' s'
==>
\s -> let r = snd# (m s)
      in
        case (# s,  r  #) of
             (# s', a' #) -> f a' s'
==>
\s -> f (snd# (m s)) s

这不是我们通常从绑定通常的惰性状态单子中得到的。 假设状态变量s带有一些真正的含义(实际上没有),它看起来更像是并发IO (or 交错IO正如函数正确所说)比lazy IO正如我们通常所说的“惰性状态单子”,其中尽管有惰性,但状态仍通过关联操作正确地线程化。

我尝试实现一个真正的惰性 IO monad,但很快意识到,为了定义 IO 数据类型的惰性 monadic 组合,我们需要能够提升/取消提升RealWorld。但这似乎是不可能的,因为两者都没有构造函数State# s and RealWorld。即使这是可能的,我也必须精确地、功能性地表达我们的现实世界,这也是不可能的。

但我仍然不确定标准 Haskell 2010 是否破坏了引用透明度,或者惰性 IO 本身就不好。至少看起来完全有可能构建一个真实世界的小型模型,在该模型上惰性 IO 是完全安全且可预测的。并且可能存在一个足够好的近似值,可以在不破坏引用透明度的情况下满足许多实际目的。


在顶部,您拥有的两个功能始终是相同的。

v1 = do !a <- x
        y

v2 = do !a <- unsafeInterleaveIO x
        y

请记住unsafeInterleaveIO推迟IO操作直到其结果被强制 - 然而您通过使用严格的模式匹配立即强制它!a,因此操作根本没有延迟。所以v1 and v2完全相同。

一般来说

一般来说,由您来证明您使用unsafeInterleaveIO是安全的。如果你打电话unsafeInterleaveIO x,那么你必须证明x可以致电any time并仍然产生相同的输出。

关于 Lazy IO 的现代感悟

...Lazy IO 很危险,而且 99% 的情况下都是个坏主意。

它试图解决的主要问题是IO必须在IOmonad,但您希望能够进行增量 IO,并且不想重写所有纯函数来调用 IO 回调来获取更多数据。增量 IO 很重要,因为它使用更少的内存,允许您对内存不适合的数据集进行操作,而无需过多更改算法。

Lazy IO 的解决方案是在外部进行 IOIO单子。这通常并不安全。

如今,人们通过使用类似的库以不同的方式解决增量 IO 的问题Conduit http://hackage.haskell.org/package/conduit or Pipes http://hackage.haskell.org/package/pipes。 Conduit 和 Pipes 比 Lazy IO 更具确定性且表现良好,可以解决相同的问题,并且不需要不安全的构造。

请记住unsafeInterleaveIO真的只是unsafePerformIO与不同的类型。

Example

下面是一个由于惰性 IO 而损坏的程序示例:

rot13 :: Char -> Char
rot13 x 
  | (x >= 'a' && x <= 'm') || (x >= 'A' && x <= 'M') = toEnum (fromEnum x + 13)
  | (x >= 'n' && x <= 'z') || (x >= 'N' && x <= 'Z') = toEnum (fromEnum x - 13)
  | otherwise = x 

rot13file :: FilePath -> IO ()
rot13file path = do
  x <- readFile path
  let y = map rot13 x
  writeFile path y

main = rot13file "test.txt"

这个节目不管用。用严格IO替换惰性IO就可以了。

Links

From 惰性 IO 破坏了纯度 http://www.haskell.org/pipermail/haskell/2009-March/021064.html作者:Oleg Kiselyov 在 Haskell 邮件列表中:

我们演示了惰性 IO 如何破坏引用透明性。一个纯粹的 类型的函数Int->Int->Int给出不同的整数,具体取决于 根据其论点的评估顺序。我们的 Haskell98 代码使用 除了标准输入之外什么都没有。我们得出的结论是,颂扬纯洁 Haskell的和广告中的lazy IO是不一致的。

...

惰性 IO 不应该被认为是好的风格。常见的之一 纯度的定义是纯表达式应计算为 无论评估顺序如何,结果都相同,或者可以是相等的 替换为等于。如果 Int 类型的表达式计算结果为 1,我们应该能够将每个出现的表达式替换为 1 不改变结果和其他可观测值。

From 惰性 IO 与正确 IO http://www.haskell.org/pipermail/haskell-cafe/2008-September/047738.html作者:Oleg Kiselyov 在 Haskell 邮件列表中:

毕竟,还有什么比这更反对的呢? Haskell 的精神,而不是具有可观察方面的“纯”函数 影响。对于 Lazy IO,人们确实必须在正确性之间做出选择 和性能。这样的代码出现特别奇怪 在 Lazy IO 出现死锁的证据之后,在此列表中呈现 不到一个月前。更不用说不可预测的资源使用和 依赖终结器来关闭文件(忘记 GHC 不 保证终结器将会运行)。

基谢廖夫写了Iteratee http://hackage.haskell.org/package/iteratee库,这是惰性 IO 的第一个真正的替代品。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

unsafeInterleaveIO 什么时候不安全? 的相关文章

  • 如何在haskell中用另一个字符串替换一个字符串

    我想用不同的字符串替换输入文件中的字符串 我正在寻找一种方法 但似乎我只能逐个字符地更改字符串 例如在我下面的代码中 replace String gt String replace replace x xs if x then y rep
  • 在 Haskell 中获取玫瑰树的根

    最近我开始学习 Haskell 并在以下练习中遇到困难 Write functions root Rose a gt a and children Rose a gt Rose a that return the value stored
  • 如何在 Haskell Pipes 中将两个 Consumer 合并为一个?

    我使用Haskell流处理库pipes https hackage haskell org package pipes编写一个命令行工具 每个命令行操作都可以将结果输出到stdout并记录到stderr with pipes API I n
  • Haskell 点运算符

    我尝试在 Haskell 中开发一个简单的平均函数 这似乎有效 lst 1 3 x fromIntegral sum lst y fromIntegral length lst z x y 但是为什么下面的版本不行呢 lst 1 3 x f
  • 如何让 do 块提前返回?

    我正在尝试使用 Haskell 抓取网页并将结果编译到一个对象中 如果出于某种原因 我无法从页面获取所有项目 我想停止尝试处理页面并提前返回 例如 scrapePage String gt IO scrapePage url do doc
  • 管道 - 将多个来源/生产者合并为一个

    我正在使用读取文件sourceFile 但我还需要在处理操作中引入随机性 我认为最好的方法是拥有一个这样的制片人 Producer m StdGen ByteString 其中 StdGen 用于生成随机数 我打算让生产者执行 source
  • 如何只修改记录的一个字段而不完全重写它? [复制]

    这个问题在这里已经有答案了 It s the second time I m tackling this problem And for the second time this is while working with the Stat
  • 在列表中查找元素及其索引

    我需要让列表的两个元素都满足谓词and这些元素的索引 我可以通过以下方式实现这一点 import Data List findIndices list Int list 3 2 4 1 9 indices findIndices gt 2
  • Python 比编译的 Haskell 更快?

    我有一个用 Python 和 Haskell 编写的简单脚本 它读取包含 1 000 000 个换行符分隔的整数的文件 将该文件解析为整数列表 对其进行快速排序 然后将其写入已排序的不同文件中 该文件与未排序的文件具有相同的格式 简单的 这
  • 使用 Parsec 解析正则表达式

    我正在尝试通过实现一个小型正则表达式解析器来学习秒差距 在 BNF 中 我的语法类似于 EXP EXP LIT EXP LIT 我尝试在 Haskell 中实现这一点 expr try star lt gt try litE lt gt l
  • 并行 Haskell - GHC GC 火花

    我有一个正在尝试并行化的程序 带有可运行代码的完整粘贴here http lpaste net 101528 我进行了分析 发现大部分时间都花在findNearest这本质上是一个简单的foldr超过一个大Data Map findNear
  • 与 Functor 不同,Monad 可以改变形状?

    我一直很喜欢以下关于单子相对于函子的力量的直观解释 单子可以改变形状 函子不能 例如 length fmap f 1 2 3 总是等于3 然而 对于单子来说 length 1 2 3 gt gt g往往不等于3 例如 如果g定义为 g Nu
  • Haskell数据类型转换问题

    我目前正在学习 Haskell 并且一直在编写一些非常简单的程序来练习 我的程序之一是 import System IO main do putStrLn Give me year y lt getLine let res show cal
  • 来自数据类型的 Haskell 随机数

    我对 Haskell 还很陌生 我有一个数据类型 data Sentence Prop Int No Sentence And Sentence Or Sentence deriving Eq 我已经为它写了一个 Show 实例 然而 无论
  • 不同类型的列表?

    data Plane Plane point Point normal Vector Double data Sphere Sphere center Point radius Double class Shape s where inte
  • 在 Haskell 中调试时打印时间戳

    我仍在学习 Haskell 并调试一些函数 并且通常有一个时间戳函数来了解某些操作何时开始和停止 doSomeAction String gt IO doSomeAction arg1 do putStrLn lt lt makeTime
  • HASKELL:解决河内塔

    下面的代码解决了 hanoi 使用预定义函数 moveLOD swapLOI 和 swapLID 返回移动列表的问题 MoveLOD 将 1 个圆盘从第一个位置移动到三元组第三个位置中的第三个销钉 此外 包含有关运动信息的字符串会堆积在字符
  • 当约束成立时,将没有约束的 GADT 转换为另一个有约束的 GADT

    我们能否将构造函数没有给定约束的 GADT 转换为具有上述约束的 GADT 我想这样做是因为我想要深度嵌入箭头并用 目前 似乎需要的表示做一些有趣的事情Typeable 一个理由 https stackoverflow com a 1223
  • seq在haskell中代表什么

    我是 Haskell 的新手 刚刚进入惰性世界编程 我读到seq函数非常特殊 因为它强制使用严格的评估 以便在某些情况下更加有效 但我就是找不到什么seq代表字面意思 也许严格评估Q 它应该提醒您 顺序 或 顺序 因为它允许程序员指定其参数
  • 告诉阴谋集团主模块在哪里

    我有一个具有以下结构的项目 foo cabal src Foo Main hs foo cabal 的一部分如下所示 executable foo main is Foo Main hs hs source dirs src Main hs

随机推荐

  • 为 iPhone 5 扩展应用程序 - 最佳实践

    现在Apple即将开始发货 iPhone 5 我正在考虑扩展我的应用程序 以便它们在 iPhone 5 上全屏显示iPhone 5 我在模拟器上运行我的应用程序 甚至是那些带有UITableView延伸至屏幕底部 屏幕顶部和底部出现黑条 这
  • 为什么这是一个未定义的行为?

    我的回答这个问题 https stackoverflow com q 18706587 845092这个函数是 inline bool divisible15 unsigned int x 286331153 2 32 1 15 40086
  • 关于 MEF 战略和结构的问题

    我的任务是模块化一个 C 应用程序 该应用程序是一个非常大的 Delphi 应用程序的重写 数据库有 249 个表 业务限制禁止对 NET 进行彻底的重新设计和更好的整体架构 因此我们基本上只是用 C 逐步重写 Delphi 应用程序的模块
  • 如何从 Selenium 获取元素的属性

    我正在 Python 中使用 Selenium 我想得到 val of a
  • 如何点击 Google Trends 中的“加载更多”按钮并通过 Selenium 和 Python 打印所有标题

    这次我想单击一个按钮来加载更多实时搜索 这是网站的链接 该按钮位于页面末尾 代码如下 div class feed load more button Load more div 由于涉及到一些 AngularJS 我不知道该怎么做 有什么提
  • tidyr:在函数内使用 mutate

    我想使用 tidyverse 中的 mutate 函数来基于旧列创建一个新列 仅使用数据框和字符串 代表列标题 作为输入 我可以在不使用 tidyverse 的情况下让它工作 参见下面的函数 f 但我想使用 tidyverse 让它工作 参
  • 抱歉,该视频无法在视频视图中播放?

    freinds 我正在使用以下代码在我的应用程序中显示 mp4 视频 并面临以下问题 我在 google 和 stackoverflow 上看到了很多与这个问题相关的帖子 但每个人都给出了自己的建议 并且没有共同的答案 1 我在模拟器中看不
  • Android ProGuard 混淆库:让类无法工作

    Intro 我在 AS 1 项目中有 2 个模型 带有一些 公共 API 类 的 Android 库项目 Android APP依赖上述库 库模块在依赖列表中 Task 我想混淆我的库项目 因为我想将其公开为公共 SDK 但又要保护我的代码
  • 将数组值与同一数组中的其他值进行比较

    我想要实现的是 它将循环遍历数组 然后它会检查数组中的项目在三个点上是否相同 product id 尺寸值和颜色值 我想创建一个新数组 其中列出了项目 我唯一不想要的是重复的值 我希望重复的值如果在这三个点上相同 则数量将被计算在一起 就像
  • 如何在 Spring MVC simpleformcontroller 上添加错误?

    我的 Spring MVC 2 5 应用程序中遇到这个问题 我不知道该怎么办 这是我的代码 public class AddStationController extends SimpleFormController private Sim
  • Android相对布局放置问题

    我在 XML 中为列表项创建相对布局以用于 ListView 中的一系列项目时遇到问题 我已经尝试了几个小时 并且正在撕扯我的头发 试图让它看起来像我想要的那样 但无法让所有东西都出现在正确的位置 而不是重叠或错位 我可以获取第一张图像和接
  • JSF 2.0:如何设置验证错误的顺序

    我正在使用 Jsf 2 和 Hibernate Validator 它工作正常 但我不知道如何设置生成的错误的顺序 举个例子 我的托管 Bean public class UserPresentation NotNull message E
  • .Net 有什么好的解析库吗?

    我正在寻找一些简单易用 语法易于定义的东西 虽然我以前没用过 ANTLR http www antlr org 有 C 运行时
  • ggplot 中的 Geom_area 顺序

    我的样本队列数据在绘制时未进行颜色编码或按正确的时间顺序显示ggplot 以下代码用于生成绘图 library ggplot2 blues lt colorRampPalette c lightblue darkblue p lt ggpl
  • 无法在 TFS 中签入 UserControl.xaml 文件。接收错误:TF10169

    Visual Studio 2013 中的项目类型是桌面应用程序 我在该桌面应用程序中添加了 XAML 格式的用户控件 我使用了一些兼容性函数和库 以便 xaml 控件能够与简单的桌面应用程序集成 TFS 正在检查其他相关文件 但是当将 x
  • 计算素数并附加到列表

    我最近开始尝试使用 python 解决 Euler 项目的问题 并且在尝试计算素数并将其附加到列表中时遇到了这个障碍 我编写了以下代码 但我很困惑为什么它在运行时不输出任何内容 import math primes def isPrime
  • ShinyApp:由对等方重置连接

    我之前构建的闪亮应用程序在我的旧笔记本电脑上运行良好 最近我买了一台装有Windows10的新笔记本电脑 设置完所有内容后 我尝试运行该应用程序 但浏览器立即打开并关闭 并出现错误 正在收听http 127 0 0 1 5004 http
  • 从自定义类导入时,XMLBeans jar 无法签名

    在 NetBeans 中 我创建了一个 Exporter 类 该类使用 APACHE POI 将一些数据导出到 EXCEL 文件 而 APACHE POI 使用 XMLBeans 我通过下载 zip 二进制文件并手动添加 jar 来添加 A
  • C# SerialPort BaseStream ReadAsync - CancellationToken 从未取消?

    我尝试以异步方式从串行端口读取数据 请记住操作所花费的时间不得超过指定的时间段 我使用的代码 private async Task
  • unsafeInterleaveIO 什么时候不安全?

    与其他不安全 操作不同 文档 http hackage haskell org packages archive base latest doc html System IO Unsafe html v unsafeInterleaveIO