我如何使用修复程序,它是如何工作的?
我对fix
的文档有点困惑(尽pipe我想我明白它现在应该做什么),所以我查看了源代码。 这让我更加困惑:
fix :: (a -> a) -> a fix f = let x = fx in x
这究竟是如何返回一个固定的点?
我决定尝试在命令行:
Prelude Data.Function> fix id ...
它挂在那里。 现在公平地说,这是我的旧的macbook,这是慢的。 然而,这个函数的计算花费不算太高,因为任何传递给id的东西都会返回相同的结果(更不要说它没有CPU时间了)。 我究竟做错了什么?
你没有做错什么。 fix id
是一个无限循环。
当我们说fix
返回一个函数的最小不动点时,我们是指在领域理论意义上。 所以fix (\x -> 2*x-1)
不会返回1
,因为尽pipe1
是该函数的一个固定点,但它并不是域中sorting的最小者 。
我不能仅仅在一两个段落中描述域的sorting,所以我会把你引向上面的域的理论链接。 这是一个很好的教程,易于阅读,相当有启发性。 我强烈推荐它。
对于10,000英尺的视图来说, fix
是一个高阶函数,它编码了recursion的思想。 如果你有这样的expression:
let x = 1:x in x
其结果是无限列表[1,1..]
,你可以用fix
来说同样的事情:
fix (\x -> 1:x)
(或简单地fix (1:)
),它说我find(1:)
函数的一个固定点,IOW一个值x
,使得x = 1:x
…就像我们上面定义的那样。 从定义中可以看出, fix
只不过是这个想法 – 将recursion封装到一个函数中。
这是一个recursion的真正的一般概念 – 你可以用这种方式编写任何recursion函数(只要它不是极其罕见的多态recursion …即使这样我不确定它是不是使用fix
可编码)。 所以例如典型的斐波那契函数:
fib n = if n < 2 then n else fib (n-1) + fib (n-2)
可以这样写:
fib = fix (\f -> \n -> if n < 2 then n else f (n-1) + f (n-2))
练习:展开fix
的定义,以显示这两个fib
定义是等价的。
但是要充分理解,请阅读域理论。 这真的很酷的东西。
我并不是说明白这一点,但是如果这有助于任何人……那么yippee。
考虑fix
的定义。 fix f = let x = fx in x
。 令人难以置信的是x
被定义为fx
。 但是想一下,
x = fx
既然x = fx,那么我们可以用右边的x
的值代替,对吧? 所以…
x = f . f $ x -- or x = f (fx) x = f . f . f $ x -- or x = f (f (fx)) x = f . f . f . f . f . f . f . f . f . f . f $ x -- etc.
所以诀窍是,为了终止, f
必须生成某种结构,以便后面的f
可以模式匹配该结构并终止recursion,而实际上并不关心其参数(?)的完整“值”
当然,除非你想像做一个无限的列表那样做,正如卢奎所说的那样。
TomMD的因子解释是很好的。 Fix的types签名是(a -> a) -> a
。 (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1)
的types签名是(b -> b) -> b -> b
,换句话说, (b -> b) -> (b -> b)
。 所以我们可以说a = (b -> b)
。 这样,修复就是使用我们的函数,它是a -> a
,或者真的, (b -> b) -> (b -> b)
,并返回a的结果,换句话说, b -> b
换句话说,另一个function!
等等,我认为它应该返回一个固定的点…不是一个函数。 那么它的确有点(因为函数是数据)。 你可以想象它给了我们find一个阶乘的明确function。 我们给它一个不知道如何recursion的函数(因此它的一个参数是一个用于recursion的函数),并且fix
它教会如何recursion。
请记住我是怎么说f
必须产生某种结构,以便以后的f
可以匹配并终止? 那么这不完全正确,我猜。 TomMD说明了我们如何扩展x
来应用这个function,并向基本情况迈进。 为了他的function,他使用了一个if / then,这就是导致终止的原因。 经过反复的replace,整个定义的部分定义最终停止以x
来定义,那就是当它是可计算和完整的时候。
您需要一种方法来终止固定点。 扩展你的例子很明显,它不会完成:
fix id --> let x = id x in x --> id x --> id (id x) --> id (id (id x)) --> ...
这是一个真正的例子,我使用修复(注意我不经常使用修复,可能是累了/不用担心可读代码,当我写这个):
(fix (\fh -> if (pred h) then f (mutate h) else h)) q
跆拳道,你说! 那么,是的,但这里有一些真正有用的观点。 首先,你的第一个fix
参数通常应该是一个“recursion”情况的函数,第二个参数是要采取行动的数据。 这是一个命名的函数相同的代码:
getQ h | pred h = getQ (mutate h) | otherwise = h
如果你仍然感到困惑,那么factorial就是一个简单的例子:
fix (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) 5 -->* 120
注意评估:
fix (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) 3 --> let x = (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x in x 3 --> let x = ... in (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x 3 --> let x = ... in (\d -> if d > 0 then d * (x (d-1)) else 1) 3
哦,你刚才看到了吗? 那个x
成了我们then
分支的一个function。
let x = ... in if 3 > 0 then 3 * (x (3 - 1)) else 1) --> let x = ... in 3 * x 2 --> let x = ... in 3 * (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x 2 -->
在上面你需要记住x = fx
,因此x 2
的两个参数在最后而不是2
。
let x = ... in 3 * (\d -> if d > 0 then d * (x (d-1)) else 1) 2 -->
我会在这里停下来!
一个明确的定义是你可以重新发明修补程序呢!
我的理解是,它为这个函数find了一个值,这样它就输出了你给它的相同的东西。 值得一提的是,它总是会selectundefined(或者是一个无穷循环,在haskell中,undefined和infinite循环都是一样的),或者是其中最没有定义的东西。 例如,用id,
λ <*Main Data.Function>: id undefined *** Exception: Prelude.undefined
正如你所看到的,undefined是一个固定的点,所以fix
会select。 如果你做(\ x-> 1:x)。
λ <*Main Data.Function>: undefined *** Exception: Prelude.undefined λ <*Main Data.Function>: (\x->1:x) undefined [1*** Exception: Prelude.undefined
所以fix
不能select未定义。 使它更多地连接到无限循环。
λ <*Main Data.Function>: let y=y in y ^CInterrupted. λ <*Main Data.Function>: (\x->1:x) (let y=y in y) [1^CInterrupted.
再次,略有不同。 那么固定点是什么? 让我们尝试repeat 1
。
λ <*Main Data.Function>: repeat 1 [1,1,1,1,1,1, and so on λ <*Main Data.Function>: (\x->1:x) $ repeat 1 [1,1,1,1,1,1, and so on
这是相同的! 由于这是唯一的固定点, fix
必须解决。 抱歉, fix
,没有无限循环或未定义你。