Haskell:列表推导中的函数

时间:2016-11-08 20:00:17

标签: haskell list-comprehension

我正在教自己Haskell,并且使用列表理解。我写了这个列表理解:

[ c | a <- [1..3], b <- [1..4], let c = hyp a b, c == round c]

我希望它会产生值列表c,其中c是一个整数(c == round c),它只有5,但它不能编译。再玩一些,我发现我无法在列表理解中嵌入任何功能,我确定有办法,我只是不知道如何。

这是错误代码:

<interactive>:158:1: error:
    • Ambiguous type variable ‘t0’ arising from a use of ‘it’
      prevents the constraint ‘(Floating t0)’ from being solved.
      Probable fix: use a type annotation to specify what ‘t0’ should be.
      These potential instances exist:
        instance Floating Double -- Defined in ‘GHC.Float’
        instance Floating Float -- Defined in ‘GHC.Float’
    • In the first argument of ‘print’, namely ‘it’
      In a stmt of an interactive GHCi command: print it

谢谢!

1 个答案:

答案 0 :(得分:2)

首先,在这样的问题中包含必要的定义。

hyp :: Floating a => a -> a -> a
hyp a b = sqrt $ a^2 + b^2

现在。 您可以在列表推导中“嵌入”功能。显然你只是选择了一些不幸的人! round具有以下类型:

GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
Prelude> :t round
round :: (Integral b, RealFrac a) => a -> b

因此,要使round c == c有意义,您需要一个同时属于IntegralRealFrac的数字类型。换句话说,这种类型将包含分数,但其所有元素都是整数。好吧,你不能吃蛋糕而且也吃它!

如果你真的写了一些类型的签名,这个问题会更加明显,就像在Haskell中经常出现的那样。虽然这样愚弄,但选择一些简单的示例类型通常会有所帮助。也许最合理的事情似乎是:

Prelude> [ c | a <- [1..3], b <- [1..4], let c = hyp a b, c == round c] :: [Integer]
<interactive>:6:41:
    No instance for (Floating Integer) arising from a use of ‘hyp’
    In the expression: hyp a b
    In an equation for ‘c’: c = hyp a b
    In the expression:
        [c |
           a <- [1 .. 3], b <- [1 .. 4], let c = hyp a b, c == round c] ::
          [Integer]

好的,所以Integer不起作用,因为你试图用hyp中的那些平方根进行真正的算术运算。 Integer无法做到这一点,您需要Floating类型的Double类型。我们试试吧:

Prelude> [ c | a <- [1..3], b <- [1..4], let c = hyp a b, c == round c] :: [Double]

<interactive>:8:55:
    No instance for (Integral Double) arising from a use of ‘round’
    In the second argument of ‘(==)’, namely ‘round c’
    In the expression: c == round c
    In a stmt of a list comprehension: c == round c

好的,这是因为正如我所说,round总是给出整数型结果。但是,您始终可以将此类整数类型再次转换为Double

Prelude> [ c | a <- [1..3], b <- [1..4], let c = hyp a b, c == fromIntegral (round c)] :: [Double]
[5.0]

请注意,这不是一个很好的解决方案:如果你已经检查过这些元素是不是完整的,你真的不希望结果是浮点数!在这种情况下,我建议不要只评估hyp。更好地利用这种理解:

Prelude> [ c | a <- [1..3], b <- [1..4], let c² = a^2 + b^2; c = round . sqrt $ fromIntegral c², c^2==c²] :: [Integer]
[5]

此版本的一个重要论点是它在Integer中进行比较,而不是在Double中进行比较。浮点平等比较是你最好远离完全的,如果你可以帮助它;在这种情况下,它的主要是无害的,因为有趣的是整数子集,实际上可以完全表示(不像0.1这样的小数部分)。你仍然可以通过这种方式得到错误的结果:特别是,对于足够大的浮点数,c == fromInteger (round c) 总是为真,因为在某个阈值之上,所有值都是积分的。< / p>

Prelude> [ c | a <- take 4 [1000000000000..], b <- take 5 [1000000000000..], let c = hyp a b, c == fromIntegral (round c)] :: [Float]
[1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12,1.4142135e12]

但这些都不是正确的积分hypothenuses,正如您在Integer中进行比较的版本所见:

Prelude> [ c | a <- take 4 [1000000000000..], b <- take 5 [1000000000000..], let c² = a^2 + b^2; c = round . sqrt $ fromIntegral c², c^2==c²] :: [Integer]
[]

严格来说,这个改进版本也不安全 - 它不会给出误报,但可能找不到实际的毕达哥拉斯三元组,因为有损浮点步骤已经可以破坏平等。要真正做到这一点,你需要一个全集的

intSqrt :: Integral a => a -> Maybe a

通过将float sqrt作为整数算术中几轮伪Newton-Raphson的起始值,这可能非常有效。

原则上,round功能也可以有更宽松的签名。

round' :: (Num b, RealFrac a) => a -> b
round' = fromInteger . round

使用该版本,您的原始代码将起作用。