Haskell 我如何使用fix,它是如何工作的?

Haskell 我如何使用fix,它是如何工作的?,haskell,fixpoint-combinators,letrec,Haskell,Fixpoint Combinators,Letrec,我对fix的文档感到有点困惑(尽管我想我现在明白了它应该做什么),所以我查看了源代码。这让我更加困惑: fix :: (a -> a) -> a fix f = let x = f x in x 这究竟是如何返回一个固定点的 我决定在命令行中尝试: Prelude Data.Function> fix id ... 它就挂在那里。公平地说,这是在我的旧macbook上,速度有点慢。然而,这个函数在计算上不会太昂贵,因为传入id的任何内容都会返回相同的内容(更不用说它不会占用

我对
fix
的文档感到有点困惑(尽管我想我现在明白了它应该做什么),所以我查看了源代码。这让我更加困惑:

fix :: (a -> a) -> a
fix f = let x = f x in x
这究竟是如何返回一个固定点的

我决定在命令行中尝试:

Prelude Data.Function> fix id
...

它就挂在那里。公平地说,这是在我的旧macbook上,速度有点慢。然而,这个函数在计算上不会太昂贵,因为传入id的任何内容都会返回相同的内容(更不用说它不会占用CPU时间)。我做错了什么?

你没有做错什么<代码>修复id是一个无限循环

当我们说
fix
返回函数的最小不动点时,我们的意思是。所以
fix(\x->2*x-1)
不会返回
1
,因为尽管
1
是该函数的固定点,但它不是域排序中的最小点

我不能仅仅用一两个段落来描述域排序,所以我会让你参考上面的域理论链接。这是一个很好的教程,容易阅读,非常有启发性。我极力推荐

对于10000英尺的视图,
fix
是一个高阶函数,它编码递归的思想。如果您有以下表达式:

let x = 1:x in x
这将导致无限列表
[1,1..]
,您可以使用
fix
说同样的话:

fix (\x -> 1:x)
(或者简单地说是
fix(1:)
),它说找到
(1:)
函数的一个固定点,通过一个
x
值,使得
x=1:x
。。。就像我们上面定义的那样。从定义中可以看出,
fix
只不过是这个概念——封装到函数中的递归

这也是递归的一个真正通用的概念——您可以用这种方式编写任何递归函数。例如,典型的斐波那契函数:

fib n = if n < 2 then n else fib (n-1) + fib (n-2)
getQ h
      | pred h = getQ (mutate h)
      | otherwise = h
练习:扩展
fix
的定义,以表明
fib
的这两个定义是等效的


但要完全理解,请阅读领域理论。这真的很酷。

您需要一种方法来终止固定点。扩展您的示例很明显,它不会完成:

fix id
--> let x = id x in x
--> id x
--> id (id x)
--> id (id (id x))
--> ...
下面是我使用fix的一个真实示例(注意,我不经常使用fix,在编写本文时可能对可读代码感到厌倦/不担心):

WTF,你说!嗯,是的,但这里有几点非常有用。首先,您的第一个
fix
参数通常应该是一个函数,它是“递归”情况,第二个参数是要执行操作的数据。以下是与命名函数相同的代码:

fib n = if n < 2 then n else fib (n-1) + fib (n-2)
getQ h
      | pred h = getQ (mutate h)
      | otherwise = h
如果您仍然感到困惑,那么阶乘可能是一个更简单的例子:

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
成为我们
分支内部的一个功能

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
,因此在结尾处是
x2
的两个参数,而不仅仅是
2

let x = ... in 3 * (\d -> if d > 0 then d * (x (d-1)) else 1) 2 -->

我就到此为止

我并不认为自己理解这一点,但如果这对任何人都有帮助的话……那就好了

考虑一下
fix
的定义<代码>修复f=让x=fx在x中
。令人难以置信的是
x
被定义为
fx
。但是想一想

x = f x
既然x=fx,那么我们可以用右边的
x
的值代替,对吗?因此

x = f . f $ x -- or x = f (f x)
x = f . f . f $ x -- or x = f (f (f x))
x = f . f . f . f . f . f . f . f . f . f . f $ x -- etc.
因此,诀窍是,为了终止,
f
必须生成某种结构,以便以后的
f
可以模式匹配该结构并终止递归,而实际上不关心其参数(?)的完整“值”

当然,除非您想创建一个无限列表,如luqui所示

TomMD的阶乘解释很好。Fix的类型签名是
(a->a)->a
(\recurse d->if d>0那么d*(recurse(d-1))else 1
的类型签名是
(b->b)->b->b
,换句话说,
(b->b)->(b->b)
。所以我们可以说
a=(b->b)
。这样,fix将使用我们的函数,即
a->a
,或者实际上是
(b->b)->(b->b)
,并将返回类型为
a
的结果,换句话说,
b->b
,换句话说,是另一个函数

等等,我以为它应该返回一个固定点…不是一个函数。是的,有点像(因为函数是数据)。你可以想象它给了我们找到阶乘的确定函数。我们给它一个不知道如何递归的函数(因此它的一个参数是用于递归的函数),然后
fix
教它如何递归


还记得我说过
f
必须生成某种结构,以便以后的
f
可以匹配和终止模式吗?我想这不完全正确。TomMD演示了如何扩展
x
以应用函数并逐步实现基本情况。对于他的功能,他使用了if/then,这就是导致终止的原因。在重复替换之后,中的
部分是整个
fix
定义的一部分,最终停止使用
x
来定义,也就是说,当它是可计算且完整的时候。

据我所知,它为函数找到了一个值,这样它就可以输出与您给它的相同的东西。问题是,它总是选择未定义的(或者无限循环,在haskell中,未定义的循环和无限循环是一样的)或者任何包含最多未定义的循环。例如,使用id

λ <*Main Data.Function>: id undefined
*** Exception: Prelude.undefined
所以
fix
不能选择未定义。使它更接近无限循环

λ <*Main Data.Function>: let y=y in y
^CInterrupted.
λ <*Main Data.Function>: (\x->1:x) (let y=y in y)
[1^CInterrupted.
是一样的!由于这是唯一的固定点,
fix
必须解决它。抱歉
修复
,没有无限循环或未定义。

恶作剧答案
λ <*Main Data.Function>: let y=y in y
^CInterrupted.
λ <*Main Data.Function>: (\x->1:x) (let y=y in y)
[1^CInterrupted.
λ <*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