函数f的定点是值x,使得f(x)= x。编写一个函数修复函数,它接受函数f并返回其定点。
例如:伪代码如下:
f(x)=
if (x=f(x)) return x
else return f(f(x))
如何使用Haskell编写它?
答案 0 :(得分:5)
从应用程序的角度来看,有很多种固定点。例如,我想区分逻辑固定点和分析固定点。该主题中的大多数答案都讨论了逻辑修复点。它可以在Haskell中写得非常漂亮如下
fix :: (a -> a) -> a
fix f = x where x = f x
甚至
fix :: (a -> a) -> a
fix f = f (fix f)
这种逻辑fix
最终成为讨论和引入语言递归的一种自然方式。
分析修正常常出现在数值计算中,并且有一些不同但相关的含义。我们将从类型开始。
fixa :: (a -> a -> Bool) -> (a -> a) -> a -> Int -> a
这显然比简单的fix
更复杂,因为它代表了守卫的血统。让我们开始编写fixa
来为这些参数命名
fixa ok iter z n
目标是重复将iter
应用于起始点z
,直到n
,正整数达到0
或ok current previous
为{{ 1}}。这个实现几乎与这里的散文完全一样。
True
像这样的函数的值是我们可以用它来做迭代数值算法,比如Newton's Method
fixa ok iter z 0 = z
fixa ok iter z n = loop z n where
loop z n =
let next = iter z
in if (ok next z)
then next
else loop next (n-1)
我们还可以通过使用Haskell的惰性评估来吐出一个懒惰的结果列表而不仅仅是最后一点来大大改善它。当我们这样做时,我们不再需要手动循环计数器,因为由消费者决定如何管理这些改进列表。
newton :: (Double -> Double) -> (Double -> Double) -> Double -> Double
newton f f' z = fixa (\a b -> a - b < 1e-6) (\x -> x - f x / f' x) z 1000
事实上,我们不再需要fixaList :: (a -> a -> Bool) -> (a -> a) -> a -> [a]
fixaList ok iter z = loop z where
loop z = let next = iter z
in if (ok next z)
then cycle next -- we'll return next forever
else z : loop next
fixa ok iter z n = fixaList ok iter z !! n
测试,也可以留给消费者
ok
现在fixaList :: (a -> a) -> a -> [a]
fixaList iter z = loop z where loop z = z : loop (iter z)
fixa iter z n = take n (fixaList iter z)
看起来有点像fixaList
fix
事实上,我们可以将fix f = x where x = f x
fixaList iter z = loop z where loop z = z : loop (iter z)
视为专业fixaList
并使用fix
来撰写
fix
这是一个很长的说法,逻辑固定点比分析固定点更强大。
答案 1 :(得分:3)
如何使用Haskell编写它?
让我们从应用程序的最后开始。所以我们希望编写递归匿名函数,例如因子,因此我们需要定点组合子。期望的结果:
fac' :: Integer -> Integer
fac' = fix fac
其中 fix 是定点组合子而 fac 是我们的阶乘组合子(不是那么匿名,但这只是为了方便)。
现在让我们定义fac。
fac :: (Integer -> Integer) -> Integer -> Integer
fac _ 1 = 1 -- Fixed point case
fac f x = x * f (x - 1)
第一个参数 f 是对fac本身的引用。现在我们可以开始推理修复。让我们从签名开始吧。在我们的示例中, fix 需要 fac 并返回一些 Integer - &gt;整数组合子。所以我们的签名如下(考虑修复可以处理任意类型的函数):
fix :: ((a -> a) -> a -> a) -> a -> a
fix f = ???
让我们来定义它。我们必须返回一些类型为 a - &gt;的 x 组合子。一个喂养一些类型的组合器 a - &gt; a 到 f 。那么,最明显的解决方案是:
fix f = x where x = f x
这是一个实际的定义。但它的签名非常复杂......请注意,可以替换 a - &gt; a by y 。然后我们得到
fix :: (y -> y) -> y
fix f = x where x = f x
这接近标准库定义。它为什么有效?考虑我们的 fac 定义的第一个案例。我们不在那里使用第一个参数,因此懒惰会阻止在固定点处的无限循环。
答案 2 :(得分:2)
从技术上讲,它可以实现为
fix f x = let x' = f x in if x == x' then x else fix f x'
你试着看到
fix (\x -> (x + 5) `div` 2) 12345
返回5
和
print $ fix (\x -> (x + 3 / x) / 2) 12345
返回1.7320508075688772
答案 3 :(得分:1)
给定函数f
,fix f
的值是多少?有点x
x = f x
。用Haskell替换上面的英语词典,我们得到了
fix f = x where x = f x
尝试fix (1:)
或fix (const 42)
。
就是这样。现在,根据定义,x
返回的任何值fix f
等于f x
。那是如果 fix f
返回一个值 - 对于许多输入它不会(尝试fix (+ 1)
)。即使对于表面上确实具有固定点的函数(例如f x = 2^(x-1)
),它也不返回值。但那是另一个故事......