通过应用等式推理,很容易看出这个定义是如何工作的。
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
实际上保留了应用程序:
结果是第二个程序需要为列表的每个元素进行分配(但由于列表立即被消耗,程序仍然有效地在恒定空间中运行):
$ ./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)
[...]