Haskell中的不动点组合子

14

固定点组合子并不总是在给定定义的情况下产生正确的答案:

fix f = f (fix f)

以下代码无法终止:

fix (\x->x*x) 0

当然,fix并不能总是得出正确的答案,但我想知道,这个问题能否得到改进?

对于上面的例子,可以实现一些类似于修复的解决方案。

fix f x | f x == f (f x)  = f x
        | otherwise       = fix f (f x)

那么为什么不使用上述定义(或者更好的东西,因为这个仅处理带有1个参数的函数)呢?

并且输出是正确的。


10
不考虑底部,固定点组合器总是返回正确的结果,但这可能不是您想要的结果。 - augustss
3
如果您对“fix”的更深层含义感兴趣,这篇文章会很有启发性:http://en.wikibooks.org/wiki/Haskell/Denotational_semantics - luqui
5个回答

24

固定点组合子会找到函数的最小定义固定点,在您的情况下,该固定点是⊥(因为非终止确实是未定义值)。

您可以检查,在您的情况下:

(\x -> x * x) ⊥ = ⊥

真的是 \x -> x * x 的不动点。

至于为什么要这样定义 fix:定义 fix 的主要目的是允许您使用匿名递归,而对此你不需要更复杂的定义。


11
需要注意的是,Haskell中fix的定义所涉及到的cpo与(例如)实数上的通常排序关系不同。它是信息论偏序,其中 ⊥ 是未定义值,是Haskell中每个(非严格)数据类型的成员,并且所有通常的实数x都满足 ⊥ ⊑ x。这是所谓的“提升离散CPO”。 - Lambdageek
1
我认为他的意思是域论,而不是信息论。 - Loki Clock

8

您的示例甚至未通过类型检查:

Prelude> fix (\x->x*x) 0

<interactive>:1:11:
    No instance for (Num (a0 -> t0))
      arising from a use of `*'
    Possible fix: add an instance declaration for (Num (a0 -> t0))
    In the expression: x * x
    In the first argument of `fix', namely `(\ x -> x * x)'
    In the expression: fix (\ x -> x * x) 0

这就是为什么它不能按你的预期工作的线索所在。你匿名函数中的 x 应该是一个函数,而不是一个数字。正如Vitus所建议的那样,修复组合器是一种在不实际编写递归的情况下编写递归的方法。总的思路是,像这样的递归定义

f x = if x == 0 then 1 else x * f (x-1)

可以写成

f    = fix (\f' x -> if x == 0  then 1 else x * f' (x-1))

您的示例

fix (\x->x*x) 0

因此对应于该表达式。
let x = x*x in x 0

这完全没有意义。

他可能想要 fix (\x->x*x)(去掉 0),这样就可以通过类型检查了。 - newacct
我怀疑这一点。他似乎期望在他修改后的“修复”下产生输出,而您的版本并没有这样做(因为函数没有“Show”实例)。我承认这只是我的猜测,他希望在那种情况下xx变成00,以某种方式使该过程停止。 - ibid

5

我并不完全有资格讨论“fixpoint combinator”是什么,或者什么是“least fixed point”,但是可以使用类似于fix的技术来近似某些函数。

《Scala by Example》第4.4节翻译成Haskell:

sqrt' :: Double -> Double
sqrt' x = sqrtIter 1.0
  where sqrtIter guess | isGoodEnough guess = guess
                       | otherwise          = sqrtIter (improve guess)
        improve guess = (guess + x / guess) / 2
        isGoodEnough guess = abs (guess * guess - x) < 0.001

这个函数通过反复“改进”一个猜测,直到我们确定它足够好为止。这个模式可以抽象出来:

myFix :: (a -> a)       -- "improve" the guess
      -> (a -> Bool)    -- determine if a guess is "good enough"
      -> a              -- starting guess
      -> a
fixApprox improve isGoodEnough startGuess = iter startGuess
  where iter guess | isGoodEnough guess = guess
                   | otherwise          = iter (improve guess)

sqrt'' :: Double -> Double
sqrt'' x = myFix improve isGoodEnough 1.0
  where improve guess = (guess + x / guess) / 2
        isGoodEnough guess = abs (guess * guess - x) < 0.001

参见Scala by Example第5.3节。 fixApprox函数可以用于逼近传入的improve函数的不动点,它会反复对输入调用improve,直到输出符合isGoodEnough条件。

实际上,你不仅可以使用myFix来逼近答案,还可以得到精确答案。

primeAfter :: Int -> Int
primeAfter n = myFix improve isPrime (succ n)
  where improve = succ
        isPrime x = null [z | z <- [2..pred x], x `rem` z == 0]

这种生成质数的方法相当愚蠢,但它可以阐明问题。嗯…现在我想知道…是否已经存在类似于myFix的东西?停一下…Hoogle时间!通过搜索 (a -> a) -> (a -> Bool) -> a -> a,第一个结果就是until

until p f 的结果是在p成立之前重复应用f的结果。

好了,你有了答案。事实证明,myFix = flip until

(请注意,“primeAfter”并不是寻找“不动点”的正确示例)注意:本翻译仅为内容翻译,不涉及任何解释。 - Dan Burton
1
+1,因为虽然您没有提到fix实际上是做什么的,但我认为这是唯一一个提供类似于OP想要的功能的答案。 - John L

1

您可能是想要使用 迭代

*Main> take 8 $ iterate (^2) (0.0 ::Float)
[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]
*Main> take 8 $ iterate (^2) (0.001 ::Float)
[1.0e-3,1.0000001e-6,1.0000002e-12,1.0000004e-24,0.0,0.0,0.0,0.0]

*Main> take 8 $ iterate (^2) (0.999 ::Float)
[0.999,0.99800104,0.9960061,0.9920281,0.9841198,0.96849173,0.93797624,0.8797994]
*Main> take 8 $ iterate (^2) (1.0 ::Float)
[1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0]
*Main> take 8 $ iterate (^2) (1.001 ::Float)
[1.001,1.002001,1.0040061,1.0080284,1.0161213,1.0325024,1.0660613,1.1364866]

在这里,您可以获得所有执行历史记录,以供分析。您可以尝试使用固定点检测。

fixed f from = snd . head 
                   . until ((< 1e-16).abs.uncurry (-).head) tail 
               $ _S zip tail history
  where history = iterate f from
        _S f g x = f x (g x)

然后

*Main> fixed (^2) (0.999 :: Float)
0.0

但是尝试使用fixed (^2) (1.001 :: Float)会无限循环,因此您需要开发单独的收敛测试,即使如此,检测到像1.0这样的斥点也需要更加复杂的调查。


0

你不能按照你所提到的方式定义fix,因为f x甚至可能无法进行比较。例如,考虑下面的例子:

myFix f x | f x == f (f x)  = f x
          | otherwise       = myFix f (f x)

addG f a b =
  if a == 0 then
    b
  else
    f (a - 1) (b + 1)

add = fix addG -- Works as expected.
-- addM = myFix addG (Compile error)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接