【问题标题】:Fixed point combinator in HaskellHaskell 中的定点组合器
【发布时间】:2019-07-25 01:14:44
【问题描述】:

给定定义,定点组合器并不总是产生正确的答案:

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 个参数的函数)的原因是什么?

【问题讨论】:

标签: haskell fixpoint-combinators fixed-point-iteration


【解决方案1】:

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

你可以检查一下,你的情况

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

确实是\x -> x * x 的不动点。

至于为什么fix是这样定义的:fix的主要目的是允许你使用anonymous recursion,为此你不需要更复杂的定义。

【讨论】:

  • 重要的是要注意,Haskell 的cpofix 的定义是有意义的,而不是(比如说)实数上通常的排序关系。相反,它是信息论偏序,其中 ⊥ 是未定义的值,它是 Haskell 中每个(非严格)数据类型的成员,所有常见的实数 x 都是 ⊥ ⊑ x。所谓的提升 离散 CPO。
  • 我认为他的意思是领域理论,而不是信息理论。
【解决方案2】:

您的示例甚至没有进行类型检查:

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-&gt;x*x)(不带0),它会进行类型检查
  • 我对此表示怀疑。他似乎期望它在他修改后的fix 下产生输出,而您的版本没有这样做(没有Show 函数实例)。我公认的推测是,他预计 x*x 在那种情况下会变成 0*0,从而以某种方式使过程短路。
【解决方案3】:

我不完全有资格谈论“定点组合器”是什么,或者“最小定点”是什么,但可以使用fix-esque 技术来近似 em> 某些功能。

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 示例第 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 这样的东西是否已经存在?停止...胡歌时间!

Hoogling (a -&gt; a) -&gt; (a -&gt; Bool) -&gt; a -&gt; a,第一个点击是until

until p f 产生应用f 直到p 成立的结果。

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

【讨论】:

  • (请注意,primeAfter 实际上并不是找到“不动点”的正确示例)
  • +1 因为,虽然你没有提到 fix 实际做了什么,但我认为这是提供类似于 OP 想要的功能的唯一答案。
【解决方案4】:

你的意思可能是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 这样的驱虫固定点的检测也需要更详细的调查。

【讨论】:

    【解决方案5】:

    你不能像你提到的那样定义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)
    

    【讨论】:

      猜你喜欢
      • 2014-05-30
      • 2011-05-15
      • 1970-01-01
      • 1970-01-01
      • 2010-09-14
      • 2020-05-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多