通過應用等式推理很容易看出這個定義是如何工作的。
fix :: (a -> a) -> a
fix f = let x = f x in x
會有什麼x
評估,當我們試圖評估fix f
?它定義爲f x
,所以fix f = f x
。但是這裏是x
?它和以前一樣是f x
。所以你得到fix f = f x = f (f x)
。通過這種方式推理,您將獲得無限的f
應用鏈:fix f
= f (f (f (f ...)))
。
現在,代(\f x -> let x' = x+1 in x:f x')
爲f
你
fix (\f x -> let x' = x+1 in x:f x')
= (\f x -> let x' = x+1 in x:f x') (f ...)
= (\x -> let x' = x+1 in x:((f ...) x'))
= (\x -> x:((f ...) x + 1))
= (\x -> x:((\x -> let x' = x+1 in x:(f ...) x') x + 1))
= (\x -> x:((\x -> x:(f ...) x + 1) x + 1))
= (\x -> x:(x + 1):((f ...) x + 1))
= ...
編輯:關於你提到的第二個問題,@ is7s指出,在評論認爲,第一個定義是可取的,因爲它是更有效的。
要找出原因,讓我們來看看核心爲fix1 (:1) !! 10^8
:
a_r1Ko :: Type.Integer
a_r1Ko = __integer 1
main_x :: [Type.Integer]
main_x =
: @ Type.Integer a_r1Ko main_x
main3 :: Type.Integer
main3 =
!!_sub @ Type.Integer main_x 100000000
正如你所看到的,在轉換之後fix1 (1:)
基本上成了main_x = 1 : main_x
。注意這個定義是如何引用的 - 這就是「綁結」的意思。這種自我參照表示爲在運行一個簡單的間接指針:
現在讓我們來看看fix2 (1:) !! 100000000
:
main6 :: Type.Integer
main6 = __integer 1
main5
:: [Type.Integer] -> [Type.Integer]
main5 = : @ Type.Integer main6
main4 :: [Type.Integer]
main4 = fix2 @ [Type.Integer] main5
main3 :: Type.Integer
main3 =
!!_sub @ Type.Integer main4 100000000
這裏fix2
應用程序實際上保留:
結果是,第二個編RAM需要爲列表中的每個元素做分配(但由於該表立即被消耗,該方案仍然有效地固定空間中運行):
$ ./Test2 +RTS -s
2,400,047,200 bytes allocated in the heap
133,012 bytes copied during GC
27,040 bytes maximum residency (1 sample(s))
17,688 bytes maximum slop
1 MB total memory in use (0 MB lost due to fragmentation)
[...]
與此相比,第一程序的行爲:
$ ./Test1 +RTS -s
47,168 bytes allocated in the heap
1,756 bytes copied during GC
42,632 bytes maximum residency (1 sample(s))
18,808 bytes maximum slop
1 MB total memory in use (0 MB lost due to fragmentation)
[...]
第一個定義更有效率,因爲它連結了一個結。例如,比較'fix1(1 :) !! 1000000'和'fix2(1 :) !! 1000000'。 – is7s
'打結'是什麼意思?你能指點我一個鏈接嗎? –
@VansonSamuel http://www.haskell.org/haskellwiki/Tying_the_Knot –