main = do
input <- sequence [getLine, getLine, getLine]
mapM_ print input
让我们看看这个程序实际运行的情况:
m@m-X555LJ:~$ runhaskell wtf.hs
asdf
jkl
powe
"asdf"
"jkl"
"powe"
让我感到惊讶的是,这里似乎没有懒惰现象。相反,所有3个getLine
都会被急切地评估,读取的值将存储在内存中,然后在打印之前,才一起输出。
与此相比:
main = do
input <- fmap lines getContents
mapM_ print input
让我们看看这个实例:
m@m-X555LJ:~$ runhaskell wtf.hs
asdf
"asdf"
lkj
"lkj"
power
"power"
完全不同的东西。逐行读取并逐行打印。这对我来说很奇怪,因为我真的看不出这两个程序之间有任何区别。
从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
递归优秀;没有任何迹象表明这个递归不应该是惰性的;就像在任何其他递归中一样,为了获取返回列表的头部,Haskell 不必穿越所有递归步骤!
对比:
rec :: Int -> [Int]
rec n = n:(rec (n+1))
main = print (head (rec 5))
实际应用场景:
m@m-X555LJ:~$ runhaskell wtf.hs
5
m@m-X555LJ:~$
显然,在这里递归是惰性执行的,而不是急切执行。
那么为什么在 sequence [getLine, getLine, getLine]
的例子中递归是急切执行的呢?
至于为什么重要的是IO操作按顺序运行,无论结果如何:想象一个动作
createFile :: IO ()
和writeToFile :: IO ()
。当我做一个sequence [createFile,writeToFile]
时,我希望它们都按顺序完成,即使我根本不关心它们实际的结果(它们的结果都是非常无聊的值()
)!
我不确定这如何适用于这个问题。
也许我会这样表达我的问题...
在我看来,这个:
do
input <- sequence [getLine, getLine, getLine]
mapM_ print input
应该将其恶化为以下内容:
do
input <- do
input <- concat ( map (fmap (:[])) [getLine, getLine, getLine] )
return input
mapM_ print input
于是,它应该会退化成类似于下面的内容(伪代码,抱歉):
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
getLine
不是惰性的。我明白了。但是我感觉我在这个问题中询问的不是我在第一个问题中询问的内容。这里的问题是:鉴于sequence
的定义方式(对getLines
列表进行递归),为什么列表不是惰性评估的?(而不是:为什么任何特定的getLine
不是惰性评估的?)也就是说-为什么第二个getLine
会在第一个getLine
之后立即被评估,而不是只有在需要时才评估?请参见我的递归生成无限列表的示例。 - user4385532IO
中,a >> b
的结果是一个 IO 操作,按照定义首先执行a
,然后执行b
。sequence as
生成的 IO 操作看起来像这样:as !! 0 >> as !! 1 >> ... >> as !! (length as - 1)
(它还将结果放在列表中,我在这里省略了这部分,因为它不重要)。无论as
还是sequence
是否惰性求值,结果值仍然相同,就像sum [1,2,3]
总是6
一样,无论[1,2,3]
如何惰性求值。 - Cubic