为什么序列[getLine,getLine,getLine]的计算没有被延迟?

gaa*_*kam 8 recursion haskell sequence lazy-evaluation

main = do
  input <- sequence [getLine, getLine, getLine]
  mapM_ print input
Run Code Online (Sandbox Code Playgroud)

让我们看看这个程序的实际效果:

m@m-X555LJ:~$ runhaskell wtf.hs
asdf
jkl
powe
"asdf"
"jkl"
"powe"
Run Code Online (Sandbox Code Playgroud)

令我惊讶的是,这里似乎没有懒惰。取而代之getLine的是,急切地评估所有3个值,将读取的值存储在内存中,然后而不是在此之前将所有值打印出来。

比较一下:

main = do
  input <- fmap lines getContents
  mapM_ print input
Run Code Online (Sandbox Code Playgroud)

让我们看一下实际情况:

m@m-X555LJ:~$ runhaskell wtf.hs
asdf
"asdf"
lkj
"lkj"
power
"power"
Run Code Online (Sandbox Code Playgroud)

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

从LearnYouAHaskell:

当I / O操作所使用,sequenceA是同样的事情sequence!它获取I / O操作的列表,并返回将执行这些操作中的每个操作的I / O操作,并以结果的形式列出这些I / O操作的结果。这是因为要将一个[IO a]值转换为一个IO [a]值,以使一个I / O操作在执行时产生结果列表,所以所有这些I / O操作都必须先进行排序,以便在评估时依次执行。被迫。如果不执行I / O操作,则无法获得结果。

我糊涂了。我不需要执行所有IO操作就可以得到一个结果。

该书前面几段显示了以下内容的定义sequence

sequenceA :: (Applicative f) => [f a] -> f [a]  
sequenceA [] = pure []  
sequenceA (x:xs) = (:) <$> x <*> sequenceA xs
Run Code Online (Sandbox Code Playgroud)

很好的递归;这里没有什么暗示我这个递归不应该是懒惰的;就像在其他任何递归中一样,要使Haskell成为返回列表的头部,不必执行所有递归步骤!

比较:

rec :: Int -> [Int]
rec n = n:(rec (n+1))

main = print (head (rec 5))
Run Code Online (Sandbox Code Playgroud)

实际上:

m@m-X555LJ:~$ runhaskell wtf.hs
5
m@m-X555LJ:~$
Run Code Online (Sandbox Code Playgroud)

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

那么为什么要sequence [getLine, getLine, getLine]急于执行示例中的递归?


关于为何无论结果如何都必须按顺序运行IO操作很重要:想象一个操作createFile :: IO ()writeToFile :: IO ()。当我执行一个操作时,sequence [createFile, writeToFile]我希望它们完成又井井有条,即使我根本不在乎它们的实际结果(都是非常无聊的价值())!

我不确定这如何适用于此问题。

也许我这样说我的Q ...

在我看来:

do
    input <- sequence [getLine, getLine, getLine]
    mapM_ print input
Run Code Online (Sandbox Code Playgroud)

应该折腾成这样的东西:

do
    input <- do
       input <- concat ( map (fmap (:[])) [getLine, getLine, getLine] )
       return input
    mapM_ print input
Run Code Online (Sandbox Code Playgroud)

反过来,应该反序列化为这样的东西(伪代码,对不起):

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
Run Code Online (Sandbox Code Playgroud)

Cub*_*bic 6

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

我糊涂了。我不需要执行所有IO操作就可以得到一个结果。

是的你是!这是的最重要的功能之一IO,如果您编写a >> b或等效地,

do a
   b
Run Code Online (Sandbox Code Playgroud)

那么您可以确定a之前绝对是“运行” b(请参见脚注)。getContents实际上是相同的,它先于其后“运行”……但是它返回的结果是一个偷偷摸摸的结果,当您尝试对其进行评估时偷偷地做更多的 IO。实际上是令人惊讶的,它在实践中可能会导致一些非常有趣的结果(例如,您正在读取文件的内容,而您正在处理的结果getContents),因此在实际程序中,您可能不应该这样做不是使用它,它主要是为了方便起见,在您不关心此类事情的程序中(例如Code Golf,一次性脚本或教学)。


关于为何无论结果如何都必须按顺序运行IO操作很重要:想象一个操作createFile :: IO ()writeToFile :: IO ()。当我执行一个操作时,sequence [createFile, writeToFile]我希望它们完成又井井有条,即使我根本不在乎它们的实际结果(都是非常无聊的价值())!


处理修改:

应该折腾成这样的东西:

do
    input <- do
       input <- concat ( map (fmap (:[])) [getLine, getLine, getLine] )
       return input
    mapM_ print input
Run Code Online (Sandbox Code Playgroud)

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

do 
  input <- do
    x <- getLine
    y <- getLine
    z <- getLine
    return [x,y,z]
  mapM_ print input
Run Code Online (Sandbox Code Playgroud)

(的实际定义sequence或多或少是这样的:

sequence [] = return []
sequence (a:as) = do
  x <- a
  fmap (x:) $ sequence as
Run Code Online (Sandbox Code Playgroud)