如何在 Haskell 中为此创建尾递归 [T = 1/x + x/2 + 3/x +x/4 + ..]

  • 本文关键字:Haskell 尾递归 创建 haskell
  • 更新时间 :
  • 英文 :


我试过这个

recursion n x = if mod n 2 == 0 
then x/n + (recursion (n-1) x)**(-1) 
else n/x + (recursion (n-1) x)**(-1)

但是有一个问题

Ambiguous type variable ‘a0’ arising from a use of ‘print’
prevents the constraint ‘(Show a0)’ from being solved.
Probable fix: use a type annotation to specify what ‘a0’ should be.
These potential instances exist:
instance Show Ordering -- Defined in ‘GHC.Show’
instance Show Integer -- Defined in ‘GHC.Show’
instance Show a => Show (Maybe a) -- Defined in ‘GHC.Show’
...plus 22 others
...plus 18 instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In a stmt of an interactive GHCi command: print it

如何解决这个问题,我的代码有什么问题?

mod n 2 == 0约束nIntegral类型,但是您随后使用x/n并且由于(/)函数具有签名(/) :: Fractional a => a -> a -> a,这意味着xn具有相同的Fractional类型。数字类型既IntegralFractional是没有意义的。

您可以使用fromIntegraln转换为Fractional类型:

recursion :: (Integral a, Floating b) => a -> b -> b
recursion n x = if mod n 2 == 0 
then x/fromIntegraln + (recursion (n-1) x)**(-1) 
elsefromIntegraln/x + (recursion (n-1) x)**(-1)

函数的另一个问题是没有停止条件。您需要添加一个额外的子句:

recursion :: (Integral a, Floating b) => a -> b -> b
recursion n x
|n <= 0 = 0
| mod n 2 == 0 = x/fromIntegraln + (recursion (n-1) x)**(-1) 
| otherwise =fromIntegraln/x + (recursion (n-1) x)**(-1)

然后,这将产生例如:

Prelude> recursion 10 2
0.3481658094621751

其他答案已经解释了这里的错误。解决这个问题的一个很好的Haskell方法如下:

import Data.List (scanl')
getNthApprox n x = approximations !! n where
approximations = scanl' (+) 0 $ fmap term [1..]
term n = if n `mod` 2 == 0
then x / fromIntegral n
else fromIntegral n / x

事实证明,由于懒惰的魔力,getNthApprox具有与尾递归相同的性能特征。这是因为scanl' (+) 0 $ fmap term [1..]的元素仅在计算approximations !! n时需要时才构造。

可能不是答案,但这个答案与标题更接近:

tSumToN :: (Enum a, Fractional a) => Int -> a -> a
tSumToN n = sum . take n . tSeq
tSeq :: (Enum a, Fractional a) => a -> [a]
tSeq x = 
interleave odds evens
where
odds = [ o / x | o <- [1,3..]]
evens = [ x / e | e <- [2,4..]]
interleave [] _ = []
interleave (y:ys) zs = y:interleave zs ys
example :: Double
example = tSumToN 4 1.1

顺便说一句:这个显然在数学上没有收敛,所以取部分和似乎毫无意义 - 但嘿,无论什么

您看到的错误是因为编译器无法找出函数的参数类型。 向函数添加类型约束可以处理此问题:

recursion :: (Integral a, Floating b) => a -> b -> b 
recursion n x = if mod n 2 == 0 
then x/fromIntegral n + (recursion (n-1) x)**(-1) 
else fromIntegral n/x + (recursion (n-1) x)**(-1)

现在函数编译但不会终止,因为没有检查终端条件 (n==0)。 要解决此问题,请添加检查。

recursion :: (Integral a, Floating b) => a -> b -> b 
recursion n x   | n == 0 = 0.0
| mod n 2 == 0 = x/fromIntegral n + (recursion (n-1) x)**(-1) 
| otherwise = fromIntegral n/x + (recursion (n-1) x)**(-1)

现在,函数将以答案终止,但答案与问题标题中所述的公式不匹配。 要解决此问题,请删除 **(-1) 。

recursion :: (Integral a, Floating b) => a -> b -> b 
recursion n x   | n == 0 = 0.0
| mod n 2 == 0 = x/fromIntegral n + (recursion (n-1) x)
| otherwise = fromIntegral n/x + (recursion (n-1) x)

现在,该函数返回正确的值。 以下主程序验证是否为这种情况:

main :: IO ()
main = do 
print $ recursion 1 1.0
print $ 1/1.0
print $ recursion 2 1.0 
print $ 1/1.0 + 1.0/2
print $ recursion 3 1.0
print $ 1/1.0 + 1.0/2 + 3/1.0
print $ recursion 4 1.0 
print $ 1/1.0 + 1.0/2 + 3/1.0 + 1.0/4

该函数返回正确的值,但不是尾递归的。 作为使其成为尾递归的第一步,请将其简化为单个递归调用。 为此,请注意公式中的项成对出现,并将它们与n-2递归一起分组。 该功能现在仅适用于n,但以后可以修补。

recursion :: (Integral a, Floating b) => a -> b -> b 
recursion n x   | n == 0 = 0.0
| otherwise = fromIntegral (n-1)/x + x/fromIntegral n + (recursion (n-2) x)

该函数仍然不是尾递归的,因为在递归调用之后会完成额外的处理(添加)。 解决此问题的一种方法是引入累加器参数来保存不完整的值。

recursion :: (Integral a, Floating b) => a -> b -> b -> b
recursion n x acc  | n == 0 = acc
| otherwise = recursion (n-2) x (fromIntegral (n-1)/x + x/fromIntegral n + acc)

作为最后一步,可以引入包装器函数来处理n的奇数值并隐藏累加器参数。 使用上述测试代码的适当修改版本进行验证。

no_recursion :: (Integral a, Floating b) => a -> b -> b 
no_recursion n x = if mod n 2 == 0 
then recursion n x 0.0
else fromIntegral n / x + recursion (n-1) x 0.0

你的意思显然是

recursion n x = if snd (properFraction (n / 2)) > 0   -- isOdd
then x/n + recursion (n-1) (x**(-1))
else n/x + recursion (n-1) (x**(-1))

但这里有两个问题。首先是正确的因为你从第一项开始n然后回到第0项,但始终使用x作为起始值,而它根据n是偶数还是奇数而有所不同。我们通过将实际工作移动到内部的"worker"函数中并修复迭代的起始值来解决此类问题。

第二个问题是它不是尾递归的。我们通过向内部"worker"函数引入累积参数来解决此类问题。一个工具解决两个问题!

-- T = 1/x + x/2 + 3/x + x/4 + ...
recursion :: RealFrac a => a -> a -> a
recursion n x = if snd (properFraction (n / 2)) > 0   -- n is odd
then goOdd  n 0
else goEven n 0
where
goOdd  n acc     = goEven (n-1) (acc + n/x)
goEven n acc 
| n <= 0    = acc
| otherwise = goOdd  (n-1) (acc + x/n)

现在它是正确的,并且随心所欲地尾递归。

最新更新