>= \_ -> getLine >>= putStr 它做了合理的事情,两次请求一个字符串,然后打印最后一个输入。因为编译器无法知道外部影响是什么getLine有,它-6ren">
gpt4 book ai didi

haskell - Haskell中的"Truly"惰性IO

转载 作者:行者123 更新时间:2023-12-03 14:56:58 25 4
gpt4 key购买 nike

考虑片段 -

getLine >>= \_ -> getLine >>= putStr

它做了合理的事情,两次请求一个字符串,然后打印最后一个输入。因为编译器无法知道外部影响是什么 getLine有,它必须执行它们,即使我们丢弃了第一个的结果。

我需要的是将 IO Monad 包装到另一个 Monad (M) 中,它允许 IO 计算有效地成为 NOP,除非使用它们的返回值。这样上面的程序就可以重写为 -
runM $ lift getLine >>= \_ -> lift getLine >>= lift putStr

在哪里
runM :: M a -> IO a
lift :: IO a -> M a

并且只要求用户输入一次。

但是,我无法弄清楚如何编写这个 Monad 来达到我想要的效果。我不确定这是否可能。有人可以帮忙吗?

最佳答案

延迟 IO 通常使用 unsafeInterleaveIO :: IO a -> IO a 来实现,它会延迟 IO 操作的副作用,直到需要它的结果,所以我们可能不得不使用它,但让我们先解决一些小问题。

首先,lift putStr不会输入检查,如 putStr有类型 String -> IO () , 和 lift有类型 IO a -> M a .我们将不得不使用像 lift . putStr 这样的东西。反而。

其次,我们必须区分应该惰性的 IO Action 和不应该惰性的 IO Action 。否则 putStr永远不会被执行,因为我们没有使用它的返回值 ()任何地方。

考虑到这一点,这似乎至少适用于您的简单示例。

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

import System.IO.Unsafe

newtype M a = M { runM :: IO a }
deriving (Monad)

lazy :: IO a -> M a
lazy = M . unsafeInterleaveIO

lift :: IO a -> M a
lift = M

main = runM $ lazy getLine >> lazy getLine >>= lift . putStr

但是,作为 C. A. McCann points out ,您可能不应该将它用于任何严重的事情。 Lazy IO 已经不受欢迎了,因为它很难推理副作用的实际顺序。这会让事情变得更加困难。

考虑这个例子
main = runM $ do
foo <- lazy readLn
bar <- lazy readLn
return $ foo / bar

读入的两个数字的顺序将完全未定义,并且可能会根据编译器版本、优化或星号的对齐方式而改变。姓名 unsafeInterleaveIO又长又丑有一个很好的理由:提醒你使用它的危险。让人们知道它何时被使用而不是将其隐藏在 monad 中是个好主意。

关于haskell - Haskell中的"Truly"惰性IO,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7191058/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com