我一直在尝试通过构建短程序来学习Haskell。我对函数式编程世界有些新意,但已经做了很多阅读。
我在Haskell中使用相对较短的递归函数,使用牛顿方法查找函数的根,直到浮点数允许的精度:
newtonsMethod :: (Ord a, Num a, Fractional a) => (a -> a) -> (a -> a) -> a -> a
newtonsMethod f f' x
| f x < epsilon = x
| otherwise =
newtonsMethod f f' (x - (f x / f' x))
where
epsilon = last . map (subtract 1) . takeWhile (/= 1)
. map (+ 1) . iterate (/2) $ 1
当我在GHCi中解释并插入newtonsMethod (\ x -> cos x + 0.2) (\ x -> -1 * sin x) (-1)
时,我得到-1.8797716370899549
,这是Newton方法的第一次迭代,用于调用值。
我的第一个问题很简单:为什么它只会递归一次?如果您发现此代码的结构方式或公然错误可能会有任何改进,请告诉我。
我的第二个问题,更多涉及,是:有一些干净的方法来测试这个函数的父调用,看看它是否未能收敛,并相应地纾困?
预先感谢您提供任何答案!
答案 0 :(得分:10)
它只运行一次,因为-1.8...
小于epsilon
,这是一个严格正数。您想检查差异的绝对值是否在容差范围内。
为此类代码进行收敛诊断的一种方法是将结果生成为惰性列表,与使用epsilon
找到iterate
的方式不同。这意味着你可以通过遍历列表来获得最终结果,但是你也可以在导致它的结果的上下文中看到它。
答案 1 :(得分:5)
我忍不住以递归的方式重写它并使用自动区分。当然应该使用AD包:http://hackage.haskell.org/package/ad。那么你不必自己计算导数,你可以看到方法收敛。
data Dual = Dual Double Double
deriving (Eq, Ord, Show)
constD :: Double -> Dual
constD x = Dual x 0
idD :: Double -> Dual
idD x = Dual x 1.0
instance Num Dual where
fromInteger n = constD $ fromInteger n
(Dual x x') + (Dual y y') = Dual (x + y) (x' + y')
(Dual x x') * (Dual y y') = Dual (x * y) (x * y' + y * x')
negate (Dual x x') = Dual (negate x) (negate x')
signum _ = undefined
abs _ = undefined
instance Fractional Dual where
fromRational p = constD $ fromRational p
recip (Dual x x') = Dual (1.0 / x) (-x' / (x * x))
instance Floating Dual where
pi = constD pi
exp (Dual x x') = Dual (exp x) (x' * exp x)
log (Dual x x') = Dual (log x) (x' / x)
sqrt (Dual x x') = Dual (sqrt x) (x' / (2 * sqrt x))
sin (Dual x x') = Dual (sin x) (x' * cos x)
cos (Dual x x') = Dual (cos x) (x' * (- sin x))
sinh (Dual x x') = Dual (sinh x) (x' * cosh x)
cosh (Dual x x') = Dual (cosh x) (x' * sinh x)
asin (Dual x x') = Dual (asin x) (x' / sqrt (1 - x*x))
acos (Dual x x') = Dual (acos x) (x' / (-sqrt (1 - x*x)))
atan (Dual x x') = Dual (atan x) (x' / (1 + x*x))
asinh (Dual x x') = Dual (asinh x) (x' / sqrt (1 + x*x))
acosh (Dual x x') = Dual (acosh x) (x' / (sqrt (x*x - 1)))
atanh (Dual x x') = Dual (atanh x) (x' / (1 - x*x))
newtonsMethod' :: (Dual -> Dual) -> Double -> [Double]
newtonsMethod' f x = zs
where
zs = x : map g zs
g y = y - a / b
where
Dual a b = f $ idD y
epsilon :: (Eq a, Fractional a) => a
epsilon = last . map (subtract 1) . takeWhile (/= 1)
. map (+ 1) . iterate (/2) $ 1
这给出了以下
*Main> take 10 $ newtonsMethod' (\x -> cos x + 0.2) (-1)
[-1.0,
-1.8797716370899549,
-1.770515242616871,
-1.7721539749707398,
-1.7721542475852199,
-1.7721542475852274,
-1.7721542475852274,
-1.7721542475852274,
-1.7721542475852274,
-1.7721542475852274]