等式の推論を適用すると、この定義がどのように機能するかを簡単に確認できます。
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))
= ...
編集: 2 番目の質問に関して、@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
アプリケーションが実際に保存されています。
その結果、2 番目のプログラムはリストの各要素の割り当てを行う必要があります (ただし、リストはすぐに消費されるため、プログラムは依然として定数空間で効果的に実行されます)。
$ ./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)
[...]