【问题标题】:Division in Haskell, still don't get itHaskell中的除法,仍然不明白
【发布时间】:2014-01-22 07:02:05
【问题描述】:

我仍然不明白 Haskell 中的划分。我的第一个意图是定义一个这样的函数:

piApprox :: (Integral a, Fractional b) => a -> b
piApprox n = 4 * sum [ (-1)^k / (2*k + 1) | k <- [0..n] ]

它不起作用。然后,使用签名:

piApprox :: (Fractional a) => Int -> a

但它再次引发“无法推断”错误。

如果我在解释器中运行这段代码来找出最好的签名,结果是:

Prelude> let piApprox n = 4 * sum [ (-1)^k / (2*k + 1) | k <- [0..n] ]
Prelude> :t piApprox
piApprox :: (Fractional a, Integral a) => a -> a

引发“类型变量‘a0’不明确”错误。

目前,我能想到的唯一进行此计算的方法是包含Ratio 包,然后使用fromRational 转换为double

import Data.Ratio
piApprox n = (fromRational) $ 4 * sum [ (-1)^k % (2*k + 1) | k <- [0..n] ]

它有效,但我认为这不是最好的方法。

我还认为即使输入和输出类型在签名中都是正确的,中间操作(-1)^k / (2*k + 1)——除法所在——可能是问题所在,所以我也定义了:

piApprox' :: (Fractional a) => Int -> a
piApprox' n = 4 * sum [ (fromIntegral) $ (-1)^k / (2*k + 1) | k <- [0..n] ]

没有运气。 我在这里错过了什么?

【问题讨论】:

  • 您可能是指Integral,而不是第一行代码中的Integer。 Haskell 中的类型之间没有自动转换,因此由于结果是b 类型,因此您需要在某处转换a,例如,使用fromIntegral k 而不是k

标签: haskell types integer int division


【解决方案1】:

您的问题是您混合了不兼容的数字类型。你说 n 是一些 Integral (特别是在这种情况下,整数)。 k &lt;- [0..n] 表示 k 是相同的 Integral 类型。然后使用/ 部门,它是Fractional 类的一部分。这意味着您的结果必须同时是IntegralFractional,而且我认为不存在这种类型。

解决方案是在将 k 与 fromIntegral k 一起使用之前将其转换为您的结果类型。

【讨论】:

    【解决方案2】:

    这应该可行:

    piApprox n = 4 * sum [ fromIntegral ((-1)^k) / fromIntegral (2*k + 1) | k <- [0..n] ]
    

    fromIntegral 函数的类型签名为:

    (Integral a, Num b) =&gt; a -&gt; b

    所以它基本上将您的 Integral 类型转换为 Num 类型。

    (/) 的类型是:

    Fractional a =&gt; a -&gt; a -&gt; a,所以你必须向它提供分数数据。

    fromIntegral 函数将通过将其转换为包含Fractional 类型的Num 类型来完全实现此目的。

    【讨论】:

    • "我们如何将Num 传递给Fractional"...对于非面向对象语言来说,这是一个糟糕的措辞。您不能“传递Num”,您可以传递恰好在Num 类型类中的类型的对象。 Haskell 没有子类型,这可能是您所暗示的。即便如此,也需要换一种方式:“将Fractional 传递给Num”。
    • @leftaroundabout 是的,我同意你的看法。你将如何更好地改写它?
    • 我只想说“fromIntegral 将整数转换为任何其他 Num 类型的数字(包括小数类型)”。阅读各种类型的类是您可以在其他地方做的事情。
    • @leftaroundabout,谢谢,我猜改写得更好。
    【解决方案3】:

    我会提倡这个签名:

    piApprox :: (Fractional r) => Int -> r
    

    原因是,“精度”参数没有任何特定的“值”含义,它只是计算您愿意让函数运行多少步的计数器。 (更好的方法可能是指定您希望允许的真实值 π偏差,而不是评估深度,但这更复杂。)

    接下来,您当前实现的冲突点实际上是(-1)^k(它需要Integral,因为求幂是通过递归乘法实现的)。是的,这是在数学和科学中表示交替符号的常用方式,但如果你仔细想想,这是一种非常糟糕的方式。你对这里的权力并不真正感兴趣,只是对符号交替感兴趣,而cycle [1, -1] 更自然地实现了这一点。

    对于乘法它是不同的,它根本不需要Integral,但要求两个参数都具有 same 类型。实现这一点的自然方法是立即使用Fractional 变量,而不是从整数转换!因此,您可以使用[0 .. fromIntegral n],而不是[0..n]每一步只需一次转换,而不是一次

    实际上,最好不要绑定索引!由于这是 Haskell,因此您可以将列表定义为无限(就像 cycle 一样)。当然你不能总结一个无限的列表,但你可以在这样做之前简单地把它修剪掉:

    piApprox :: (Fractional r) => Int -> r
    piApprox n = 4 * sum (take n [ σ / (2*k + 1) | (σ,k) <- zip (cycle [-1,1]) [0..] ])
    

    或者,也许用ParallelListComprehensions extension 写得更好:

    piApprox n = (4 *) . sum $
        take n [ σ / (2*k + 1) | σ <- cycle [1, -1]
                               | k <- [0..]
               ]
    

    这比你的实现少了一步,因为take n [0..] 等同于[0..n-1]。我想这无关紧要,否则修复起来很简单。


    最后:我假设你知道这个公式在收敛速度方面非常糟糕!

    【讨论】:

      猜你喜欢
      • 2014-08-05
      • 1970-01-01
      • 2012-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多