簡體   English   中英

Haskell中IO的無限循環如何工作

[英]How infinite loop with IO work in haskell

在haskell中,我們編寫的所有IO代碼都只是一個動作(許多建議將其視為正在生成的腳本)。 它是最終執行它們(執行構造的腳本)的主要方法。 那么以下程序如何工作? infi函數將永遠不會返回。 那么,為什么字符串會無限打印呢?

infi = 
    do
     print "hello"
     infi

main = 
    do
     infi

您似乎對Haskell如何實際實現IO產生了誤解。 這個主題上還有很多其他文獻,與此站點有關的其他答案也很多,因此,我將重點介紹您的具體示例,而不是一般示例。

infi = 
  do
   print "hello"
   infi

main = 
  do
    infi

首先,您可以簡化您的主要動作(無需do ):

main = infi 

在haskell中,“返回”與命令式“返回”不同。 它只是意味着注入單子動作,即return :: Monad m => a -> ma 因此,讓我們談談這里評估的是什么,而不是什么時候返回。

您所做的所有main功能就是調用infi :: IO ()類型的值infi 由於infiIO操作,因此可以執行打印。 像任何其他值一樣,它也可以引用其他值(在這種情況下,它是遞歸的,因此可以調用自身)。 沒有基本情況,INFI將繼續執行以下序列(就像在您的do塊中所示!):

  1. 打印“你好”到STDOUT
  2. 評估值infi
    1. 打印“你好”到STDOUT
    2. 評估值infi
      ...堆棧將永遠繼續,因為遞歸中沒有基本情況。

之所以可行,主要是因為Haskell的懶惰評估。 Haskell永遠不會真正計算值,直到您需要它為止。 這就是為什么您也可以對無限列表執行純操作:

let x = [1..] -- x is an infinite list. If you told haskell to print every element, it would run forever since it would evaluate the whole thing. 
let y = x !! 3 -- y = 2. This is not infinite because you are only evaluating the first three elements, instead of the whole value.

您的無限值infi也是如此。 Haskell可以創建一個包含無限操作的“運行時腳本”,因為infi的具有有限的表示形式(其名稱),但是由於沒有基本情況,因此它的計算結果是無限的。

infi是基本上相同的無限流IO動作,與一元測序(連接>> )代替缺點( : ):

fives = 5                :  fives
infi  = putStrLn "hello" >> infi

實際上,我們可以使用一系列infi'動作抽象出monadic綁定:

infi' :: [IO ()]
infi' = putStrLn "hello" : infi'

然后使用sequence_恢復infi ,該sequence_可以實現為foldr (>>) (return ())

infi = sequence_ infi'
infi = (foldr (>>) (return ()) infi')
infi = putStrLn "hello" >> (foldr (>>) (return ()) infi')
infi = putStrLn "hello" >> (putStrLn "hello" >> (foldr (>>) (return ()) infi'))
infi = putStrLn "hello" >> (putStrLn "hello" >> (putStrLn "hello" >> ...))

將動作存儲在這樣的流中還可以讓您將它們作為一流的值進行操作:

> sequence_ (take 3 infi')
hello
hello
hello

當Haskell運行時執行您的main操作時,它將計算infi ,找到>>表達式,計算其左手參數以產生操作putStrLn "hello"執行該操作,然后繼續執行右手參數-再次成為infi 評估由IOMonad實例中的內部模式匹配懶惰地驅動。

它與程序非常相似

ones = 1 : ones

上面是遞歸的,是的。 它會無限多次調用自己,是的。 但是它確實會返回。 它返回一個無限列表。 通過對比,

noList = noList

將永遠循環而不會返回列表。 (實際上,GHC運行時會檢測到此情況並引發異常,但這與討論無關。)

同樣,

printOnes = print 1 >> printOnes
-- or, equivalently
printOnes = do
   print 1
   printOnes

建立一個IO操作,該操作將永遠打印1 ,即使它無限次遞歸多次也是如此。 代替,

noPrint = noPrint

會永遠循環並且永遠不會返回IO操作。

因為將infi設置為遞歸調用其自身。 從main預期的“返回”將永遠不會實現,因為對infi的首次調用將永遠不會返回。

# The first call to infi will never return as the calls to infi
# will just continue to add more calls to the stack, until you exceed
# the size :)

main ->
  infi ->
    infi ->
      infi -> ..

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM