在幕後,Haskell系統通常用一種稱爲thunk的結構來表示內存中的值。一個thunk看作一個對象,該對象具有兩種狀態:
- Uncomputed
- 已計算
一種uncomputed形實轉換包含一個指向其計算其結果的目標代碼子例程,和一個指針thunk提供執行該計算所需的值。 (如果您遇到closures的概念,則未計算的thunk就是關閉。)
計算的thunk僅包含原始結果值。在thunk上的基本操作被稱爲強迫。當你強制一個未計算的thunk時,它的子程序被捕獲的參數調用,thunk被替換爲計算結果值(從而將其狀態轉換爲計算值),並返回該值。當你強制一個已經計算的thunk時,你只需要獲得已經計算的值。
如果我們用僞代碼編寫,也許這樣更容易。我會做一些Java的ISH:
class Thunk<A> {
private final Supplier<A> computation;
private boolean computed = false;
private A result;
Thunk(Supplier<A> computation) {
this.computation = computation;
}
public A force() {
if (!computed) {
result = computation.get();
computed = true;
}
return result;
}
}
這不正好就是上面描述我的東西,但它確實表現得像它。
現在,讓我們來看一個簡單的例子,即構建一個元素的重複列表的功能:
repeat :: a -> [a]
repeat a = a : repeat a
的repeat
功能被編譯成目標代碼程序,在僞代碼,可能看起來是這樣的:
Thunk<List<A>> repeat(Thunk<A> a) {
return new Thunk<A>(() -> new Cons<A>(a, repeat(a)));
}
class Cons<A> extends List<A> {
Thunk<A> head;
Thunk<List<A>> tail;
// ...
}
如果您不熟悉與Java 8,() -> new Cons<A>(a, repeat(a))
是一個lambda。這是一個函數,它接受零參數,並在被調用時構造一對。遞歸調用repeat
在內部爲,因此調用repeat
不會遞歸 - 它返回一個Thunk
,它捕獲lambda,而不立即執行它。當該thunk爲force
d時,只有然後將調用lambda,這將調用repeat
,這將返回另一個類似的thunk馬上。
基本上,在Haskell中,代碼被編譯爲一個優化的低級版本。
[此程序員在堆棧交換站點中的線程](http://programmers.stackexchange.com/questions/304019/how-does-repeat-x-xrepeat-x-return-a-list-in-haskell/ )可能會有所幫助。 –