首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么序列[getLine,getLine,getLine]没有懒惰地进行评估?

为什么序列[getLine,getLine,getLine]没有懒惰地进行评估?
EN

Stack Overflow用户
提问于 2019-04-20 09:01:52
回答 2查看 154关注 0票数 9
代码语言:javascript
复制
main = do
  input <- sequence [getLine, getLine, getLine]
  mapM_ print input

让我们看看这个计划的实施情况:

代码语言:javascript
复制
m@m-X555LJ:~$ runhaskell wtf.hs
asdf
jkl
powe
"asdf"
"jkl"
"powe"

令我惊讶的是,这里似乎没有懒惰。相反,所有3个getLine都被急切地计算,读取值存储在内存中,然后,而不是以前,所有的都被打印出来。

与此相比:

代码语言:javascript
复制
main = do
  input <- fmap lines getContents
  mapM_ print input

让我们来看看它的作用:

代码语言:javascript
复制
m@m-X555LJ:~$ runhaskell wtf.hs
asdf
"asdf"
lkj
"lkj"
power
"power"

完全不同的东西。一行一个一个地读,然后一个一个地打印出来。这对我来说很奇怪,因为我看不出这两个程序之间有什么区别。

来自LearnYouAHaskell:

当与I/O操作一起使用时,sequenceAsequence是一样的!它接受I/O操作的列表,并返回一个I/O操作,该操作将执行每个操作,并有这些I/O操作的结果列表。这是因为要将[IO a]值转换为IO [a]值,要使I/O操作在执行时生成结果列表,必须对所有这些I/O操作进行排序,以便在强制求值时依次执行它们。如果不执行I/O操作,则无法获得结果。

我很困惑。我不需要执行所有的IO操作就能得到一个结果。

在前面的几段中,本书给出了sequence的定义

sequenceA ::(应用f) => f a -> f a sequenceA [] =纯[] sequenceA (x: xs ) = (:) <$> x <*> sequenceA xs

不错的递归;这里没有什么暗示我这个递归不应该懒惰;就像在任何其他递归中一样,要获得返回列表的头,Haskell不需要遍历递归的所有步骤!

比较:

代码语言:javascript
复制
rec :: Int -> [Int]
rec n = n:(rec (n+1))

main = print (head (rec 5))

在行动中:

代码语言:javascript
复制
m@m-X555LJ:~$ runhaskell wtf.hs
5
m@m-X555LJ:~$

显然,这里的递归是懒散地执行的,而不是急切地执行的。

那么,为什么急切地执行sequence [getLine, getLine, getLine]示例中的递归呢?

至于为什么无论结果如何,按顺序运行IO操作是很重要的:想象一个动作createFile :: IO ()writeToFile :: IO ()。当我做一个sequence [createFile, writeToFile]时,我希望它们都做得井井有条,尽管我根本不关心它们的实际结果(这两者都是非常无聊的值())!

我不知道这如何适用于这个Q。

也许我会这样回答我的问题..。

在我看来:

代码语言:javascript
复制
do
    input <- sequence [getLine, getLine, getLine]
    mapM_ print input

应该绕道去做这样的事情:

代码语言:javascript
复制
do
    input <- do
       input <- concat ( map (fmap (:[])) [getLine, getLine, getLine] )
       return input
    mapM_ print input

这反过来又应该转到类似这样的东西(伪代码,对不起):

代码语言:javascript
复制
do
    [ perform print on the result of getLine,
      perform print on the result of getLine,
      perform print on the result of getLine
    ] and discard the results of those prints since print was applied with mapM_ which discards the results unlike mapM
EN

回答 2

Stack Overflow用户

发布于 2019-04-20 09:20:34

getContents懒惰,getLine不懒惰。懒惰IO本身不是Haskell的特性,它是某些特定IO操作的特性。

我很困惑。我不需要执行所有的IO操作就能得到一个结果。

不,你知道!这是IO最重要的特性之一,如果您编写a >> b或等效的话,

代码语言:javascript
复制
do a
   b

然后,您可以确定a肯定是在b之前“运行”(见脚注)。getContents实际上是一样的,它“运行”在它之后的任何东西之前.但是它返回的结果是一个狡猾的结果,当您试图评估它时,它会偷偷地执行更多的IO操作。这实际上是一个令人惊讶的地方,它可能在实践中导致一些非常有趣的结果(比如在处理getContents的结果时,您正在读取被删除或更改的内容),所以在实际程序中,您可能不应该使用它,在您不关心这些事情的程序中,它主要是为了方便而存在的(例如,代码高尔夫、丢弃脚本或教学)。

至于为什么无论结果如何,按顺序运行IO操作是很重要的:想象一个动作createFile :: IO ()writeToFile :: IO ()。当我做一个sequence [createFile, writeToFile]时,我希望它们都做得井井有条,尽管我根本不关心它们的实际结果(这两者都是非常无聊的值())!

寻址编辑:

应该绕道去做这样的事情: do输入<- do输入<- concat ( map (fmap (:[])) getLine,getLine,getLine )返回输入mapM_打印输入

不,它实际上变成了这样的东西

代码语言:javascript
复制
do 
  input <- do
    x <- getLine
    y <- getLine
    z <- getLine
    return [x,y,z]
  mapM_ print input

( sequence的实际定义或多或少如下:

代码语言:javascript
复制
sequence [] = return []
sequence (a:as) = do
  x <- a
  fmap (x:) $ sequence as
票数 6
EN

Stack Overflow用户

发布于 2019-04-20 09:34:35

技术上,在

代码语言:javascript
复制
sequenceA (x:xs) = (:) <$> x <*> sequenceA xs

我们找到<*>,它首先在左边运行操作,然后在右边运行操作,最后一起应用它们的结果。这就是使列表中的第一个效果首先发生的原因,依此类推。

实际上,在monads上,f <*> x相当于

代码语言:javascript
复制
do theF <- f
   theX <- x
   return (theF theX)

更一般地,请注意,所有IO操作通常都是按照顺序执行的,首先要执行到最后(几个罕见的例外情况见下文)。对程序员来说,以一种完全懒散的方式做IO将是一场噩梦。例如,考虑:

代码语言:javascript
复制
do let aX = print "x" >> return 4
       aY = print "y" >> return 10
   x <- aX
   y <- aY
   print (x+y)

按照这个顺序,Haskell保证输出是x y 14。如果我们有完全懒惰的IO,我们也可以得到y x 14,这取决于哪个参数首先是由+强制的。在这种情况下,我们需要确切地知道每个操作要求懒惰块的顺序,这是程序员绝对不想关心的事情。在这样详细的语义下,x + y不再等同于y + x,在许多情况下打破了等式推理。

现在,如果我们想强迫IO懒惰,我们可以使用一个被禁止的函数。

代码语言:javascript
复制
do let aX = unsafeInterleaveIO (print "x" >> return 4)
       aY = unsafeInterleaveIO (print "y" >> return 10)
   x <- aX
   y <- aY
   print (x+y)

上面的代码使aXaY延迟了IO操作,输出的顺序现在是编译器和+库实现的随心所欲。这通常是危险的,因此懒惰IO的unsafe性质。

现在,关于例外。一些仅从环境读取的IO操作(如getContents )是用惰性IO (unsafeInterleaveIO)实现的。设计人员认为,对于这样的读取,延迟IO是可以接受的,并且在许多情况下,读取的精确时间并不那么重要。

现在,这是controversial。虽然它很方便,但在许多情况下,懒惰的IO可能太不可预测了。例如,我们无法知道文件将在何处关闭,如果我们从套接字读取,这可能很重要。我们还需要非常小心,不要过早地强制读取:这通常会导致从管道读取时出现死锁。如今,人们通常倾向于避免懒惰的IO,并求助于诸如pipesconduit这样的库来进行“流”-like操作,在这些操作中没有任何歧义。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/55771942

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档