yield是生成器中的特有用法,而生成器是一種可以封閉整個執行狀態、可以隨時暫停繼續的模型,從傳統的程式觀點是很難描述它的,但是似乎跟函數語言程式設計有比較密切的關係(雖然我並沒有學過函數語言程式設計……)設想我們有一個函式,它返回一個序列,這個序列可以是無限長的(也可以是有限長)。當然,無限長的序列我們是表示不出來的,記憶體會爆。但是我們通常可以把它表示成一個廣義表,它的第一項是下一個值,而第二項是剩下所有值用同樣方法形成的廣義表,也就是說我們把返回值:改寫成如果原始的序列是有限長的,則最終某個子表裡只有一個元素。這樣的形式對傳統的程式來說似乎沒什麼用,但是一般我們認為廣義表是可以延遲求值的,也就是說我們可以每次取一個值,然後需要的時候再去計算下一個子表。這個模型就對應到了生成器。我們每次呼叫生成器讓它返回下一個值的時候,就相當於取出子表中第一項的同時,將生成器推進到了下一個子表中,這樣我們得生成器就可以返回任意有限甚至無限多個元素,而且只在需要的時候才計算出它們。接下來我們知道,返回一個子表,和返回一個“返回子表的函式”,其實沒有什麼區別。那麼如果返回的這個函式還能接受引數呢?我們可以在獲取上一個值之後,給這個生成器傳入一個新的值,從而影響之後返回的結果,這個就是yield表示式的作用了,它返回一個值,這個值實際上是從外部傳入的,也就是我們看到的輸入的引數。但是生成器其實又跟真正的函數語言程式設計不同,它是在傳統程式設計方法當中實現一個這樣功能結構的語法,真正的函數語言程式設計自然會毫不猶豫地將它寫成尾遞迴的形式。但我們也知道,尾遞迴可以轉化成迴圈,那麼生成器通常就是將尾遞迴轉化成迴圈之後的形式,它的內部封閉了所有本來應該在遞迴中作為引數傳遞的狀態,這樣我們可以用傳統的程式設計的方法來寫這個生成器,這樣在許多時候是比較方便的,比如說問題異常複雜,比如說需要呼叫I/O等非冪等的方法的時候。由於生成器的第二種形式可以看成每次都隱含返回一個接受引數的函式,這個函式可以代替非同步程式設計中的回撥函式,從而用生成器編寫非同步過程,這種方法許多時候也被叫做協程,但從一開始就強調生成器的這種用法我覺得是不科學的,它其實只是用生成器代替了非同步回撥函式而已,並不是自己就具有獨立執行的能力,把生成器叫做協程容易讓初學者忽略了排程器在非同步程式中的重要作用,造成誤導。
yield是生成器中的特有用法,而生成器是一種可以封閉整個執行狀態、可以隨時暫停繼續的模型,從傳統的程式觀點是很難描述它的,但是似乎跟函數語言程式設計有比較密切的關係(雖然我並沒有學過函數語言程式設計……)設想我們有一個函式,它返回一個序列,這個序列可以是無限長的(也可以是有限長)。當然,無限長的序列我們是表示不出來的,記憶體會爆。但是我們通常可以把它表示成一個廣義表,它的第一項是下一個值,而第二項是剩下所有值用同樣方法形成的廣義表,也就是說我們把返回值:改寫成如果原始的序列是有限長的,則最終某個子表裡只有一個元素。這樣的形式對傳統的程式來說似乎沒什麼用,但是一般我們認為廣義表是可以延遲求值的,也就是說我們可以每次取一個值,然後需要的時候再去計算下一個子表。這個模型就對應到了生成器。我們每次呼叫生成器讓它返回下一個值的時候,就相當於取出子表中第一項的同時,將生成器推進到了下一個子表中,這樣我們得生成器就可以返回任意有限甚至無限多個元素,而且只在需要的時候才計算出它們。接下來我們知道,返回一個子表,和返回一個“返回子表的函式”,其實沒有什麼區別。那麼如果返回的這個函式還能接受引數呢?我們可以在獲取上一個值之後,給這個生成器傳入一個新的值,從而影響之後返回的結果,這個就是yield表示式的作用了,它返回一個值,這個值實際上是從外部傳入的,也就是我們看到的輸入的引數。但是生成器其實又跟真正的函數語言程式設計不同,它是在傳統程式設計方法當中實現一個這樣功能結構的語法,真正的函數語言程式設計自然會毫不猶豫地將它寫成尾遞迴的形式。但我們也知道,尾遞迴可以轉化成迴圈,那麼生成器通常就是將尾遞迴轉化成迴圈之後的形式,它的內部封閉了所有本來應該在遞迴中作為引數傳遞的狀態,這樣我們可以用傳統的程式設計的方法來寫這個生成器,這樣在許多時候是比較方便的,比如說問題異常複雜,比如說需要呼叫I/O等非冪等的方法的時候。由於生成器的第二種形式可以看成每次都隱含返回一個接受引數的函式,這個函式可以代替非同步程式設計中的回撥函式,從而用生成器編寫非同步過程,這種方法許多時候也被叫做協程,但從一開始就強調生成器的這種用法我覺得是不科學的,它其實只是用生成器代替了非同步回撥函式而已,並不是自己就具有獨立執行的能力,把生成器叫做協程容易讓初學者忽略了排程器在非同步程式中的重要作用,造成誤導。