Haskell中的定点组合子

时间:2011-11-11 20:09:03

标签: haskell fixed-point combinators

根据定义,定点组合器并不总能产生正确的答案:

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个参数的处理函数)不会被使用?

5 个答案:

答案 0 :(得分:22)

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

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

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

即。 确实是\x -> x * x的固定点。

至于为什么fix以这种方式定义:fix的要点是允许您使用anonymous recursion,为此您不需要更复杂的定义。

答案 1 :(得分:7)

你的例子甚至没有进行类型检查:

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

没有任何意义。

答案 2 :(得分:4)

我没有完全有资格谈论&#34; fixpoint combinator&#34;是,或者&#34;最不固定点&#34;是,但可以使用fix - esque技术近似某些功能。

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

此功能可反复使用&#34;改进&#34;一直猜测,直到我们确定它足够好&#34;。这种模式可以抽象出来:

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时间!

Hoogling (a -> a) -> (a -> Bool) -> a -> a,第一次点击是until

  

until p f会产生在f成立之前应用p的结果。

那么你有它。事实证明,myFix = flip until

答案 3 :(得分:1)

你可能意味着iterate

*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这样的驱避固定点也需要更精细的调查。

答案 4 :(得分: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)